From b6be6472f9b8a4be99a3bce111415def41998d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Roumezin?= <94066559+RaphaelRoumezin@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:06:05 +0200 Subject: [PATCH] fix: Change model to use i16 newtype for `OrganizationCollectionSettings` --- src/api/core/organizations.rs | 29 ++++++--- src/db/models/cipher.rs | 3 +- src/db/models/mod.rs | 4 +- src/db/models/organization.rs | 112 ++++++++++++---------------------- 4 files changed, 63 insertions(+), 85 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index caed3705..9d0262d3 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -6,9 +6,9 @@ use serde_json::Value; use crate::{ CONFIG, - api::admin::FAKE_ADMIN_UUID, api::{ EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType, + admin::FAKE_ADMIN_UUID, core::{CipherSyncData, CipherSyncType, accept_org_invite, log_event, two_factor}, }, auth::{AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OrgMemberHeaders, OwnerHeaders, decode_invite}, @@ -17,7 +17,8 @@ use crate::{ models::{ Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType, Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType, - OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId, + OrgCollectionSetting, OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, + UserId, }, }, mail, @@ -367,11 +368,16 @@ async fn put_organization_collection_management( err!("Organization not found") }; - org.collection_settings.allow_admin_access_to_all_collection_items = - data.allow_admin_access_to_all_collection_items.unwrap_or(false); - org.collection_settings.limit_collection_creation = data.limit_collection_creation.unwrap_or(false); - org.collection_settings.limit_collection_deletion = data.limit_collection_deletion.unwrap_or(false); - org.collection_settings.limit_item_deletion = data.limit_item_deletion.unwrap_or(false); + org.collection_settings.set_flag( + OrgCollectionSetting::AllowAdminAccessToAllCollectionItems, + data.allow_admin_access_to_all_collection_items.unwrap_or(false), + ); + org.collection_settings + .set_flag(OrgCollectionSetting::LimitCollectionCreation, data.limit_collection_creation.unwrap_or(false)); + org.collection_settings + .set_flag(OrgCollectionSetting::LimitCollectionDeletion, data.limit_collection_deletion.unwrap_or(false)); + org.collection_settings + .set_flag(OrgCollectionSetting::LimitItemDeletion, data.limit_item_deletion.unwrap_or(false)); org.save(&conn).await?; @@ -560,7 +566,7 @@ async fn post_organization_collections( }; if headers.membership.atype == MembershipType::Manager - && (!headers.membership.access_all || org_settings.limit_collection_creation) + && (!headers.membership.access_all || org_settings.get_flag(OrgCollectionSetting::LimitCollectionCreation)) { err!("You don't have permission to create collections") } @@ -775,7 +781,9 @@ async fn delete_organization_collection_impl( err!("Collection not found", "Collection does not exist or does not belong to this organization") }; - if headers.membership_type <= MembershipType::Manager && org_settings.limit_collection_deletion { + if headers.membership_type <= MembershipType::Manager + && org_settings.get_flag(OrgCollectionSetting::LimitCollectionDeletion) + { err!("The current user isn't allowed to delete this collection") } @@ -1898,7 +1906,8 @@ async fn post_org_import( // We do not allow users or managers which can not manage all collections to create new collections // If there is any collection other than an existing import collection, abort the import. if headers.membership.atype <= MembershipType::Manager - && (!headers.membership.has_full_access() || org_settings.limit_collection_creation) + && (!headers.membership.has_full_access() + || org_settings.get_flag(OrgCollectionSetting::LimitCollectionCreation)) { err!(Compact, "The current user isn't allowed to create new collections") } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index fcb7f30e..483dfa7b 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -13,6 +13,7 @@ use crate::{ }, db::{ DbConn, + models::OrgCollectionSetting, schema::{ ciphers, ciphers_collections, collections, collections_groups, folders, folders_ciphers, groups, groups_users, users_collections, users_organizations, @@ -728,7 +729,7 @@ impl Cipher { pub async fn is_deletable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool { let manage_required = match self.organization_uuid { Some(ref org_id) => match Organization::find_collection_settings_by_uuid(org_id, conn).await { - Some(settings) => settings.limit_item_deletion, + Some(settings) => settings.get_flag(OrgCollectionSetting::LimitItemDeletion), None => false, }, None => false, diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 0ed8ef91..f42f3aad 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -31,8 +31,8 @@ pub use self::folder::{Folder, FolderCipher, FolderId}; pub use self::group::{CollectionGroup, Group, GroupId, GroupUser}; pub use self::org_policy::{OrgPolicy, OrgPolicyId, OrgPolicyType}; pub use self::organization::{ - Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey, - OrganizationId, + Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, OrgCollectionSetting, Organization, + OrganizationApiKey, OrganizationId, }; pub use self::send::{Send, SendFileId, SendId, SendType}; pub use self::sso_auth::{OIDCAuthenticatedUser, OIDCCodeResponseError, SsoAuth}; diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 14e37621..710d41f8 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -5,15 +5,9 @@ use std::{ use chrono::{NaiveDateTime, Utc}; use derive_more::{AsRef, Deref, Display, From}; -use diesel::{ - deserialize::FromSql, - prelude::*, - serialize::{IsNull, ToSql}, - sql_types::SmallInt, -}; +use diesel::{deserialize::FromSql, prelude::*, serialize::ToSql, sql_types::SmallInt}; use num_traits::FromPrimitive; #[cfg(feature = "sqlite")] -use num_traits::ToPrimitive; use serde_json::Value; use crate::{ @@ -35,59 +29,39 @@ use super::{ OrgPolicyType, TwoFactor, User, UserId, }; +#[repr(i16)] +pub enum OrgCollectionSetting { + AllowAdminAccessToAllCollectionItems = 0b1, + LimitCollectionCreation = 0b10, + LimitCollectionDeletion = 0b100, + LimitItemDeletion = 0b1000, +} + #[derive(Debug, Clone, Copy, FromSqlRow, AsExpression)] #[diesel(sql_type = SmallInt)] -#[allow(clippy::struct_excessive_bools)] -pub struct OrganizationCollectionSettings { - pub allow_admin_access_to_all_collection_items: bool, - pub limit_collection_creation: bool, - pub limit_collection_deletion: bool, - pub limit_item_deletion: bool, -} +pub struct OrganizationCollectionSettings(i16); -impl From<&OrganizationCollectionSettings> for i16 { - fn from(value: &OrganizationCollectionSettings) -> Self { - i16::from(value.allow_admin_access_to_all_collection_items) - | (0b10 * i16::from(value.limit_collection_creation)) - | (0b100 * i16::from(value.limit_collection_deletion)) - | (0b1000 * i16::from(value.limit_item_deletion)) +impl OrganizationCollectionSettings { + pub fn get_flag(self, flag: OrgCollectionSetting) -> bool { + (self.0 | flag as i16) > 0 } -} -#[cfg(feature = "sqlite")] -use diesel::sqlite::Sqlite; -#[cfg(feature = "sqlite")] -impl ToSql for OrganizationCollectionSettings -where - i16: ToSql, -{ - fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, Sqlite>) -> diesel::serialize::Result { - out.set_value(i16::from(self).to_i32()); - Ok(IsNull::No) - } -} - -#[cfg(feature = "postgresql")] -use diesel::pg::Pg; -#[cfg(feature = "postgresql")] -impl ToSql for OrganizationCollectionSettings -where - i16: ToSql, -{ - fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, Pg>) -> diesel::serialize::Result { - >::to_sql(&i16::from(self), &mut out.reborrow()) + pub fn set_flag(&mut self, flag: OrgCollectionSetting, val: bool) { + if val { + self.0 |= flag as i16; + } else { + self.0 &= i16::MAX - flag as i16; + } } } -#[cfg(feature = "mysql")] -use diesel::mysql::Mysql; -#[cfg(feature = "mysql")] -impl ToSql for OrganizationCollectionSettings +impl ToSql for OrganizationCollectionSettings where - i16: ToSql, + DB: diesel::backend::Backend, + i16: ToSql, { - fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, Mysql>) -> diesel::serialize::Result { - >::to_sql(&i16::from(self), &mut out.reborrow()) + fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, DB>) -> diesel::serialize::Result { + self.0.to_sql(out) } } @@ -98,12 +72,7 @@ where { fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result { let val = i16::from_sql(bytes)?; - Ok(Self { - allow_admin_access_to_all_collection_items: (val & 0b1) != 0, - limit_collection_creation: (val & 0b10) != 0, - limit_collection_deletion: (val & 0b100) != 0, - limit_item_deletion: (val & 0b1000) != 0, - }) + Ok(Self(val)) } } @@ -274,12 +243,11 @@ impl Organization { billing_email, private_key, public_key, - collection_settings: OrganizationCollectionSettings { - allow_admin_access_to_all_collection_items: true, - limit_collection_creation: false, - limit_collection_deletion: false, - limit_item_deletion: false, - }, + + // Collection Management Settings default to AllowAdminAccessToAllCollectionItems + collection_settings: OrganizationCollectionSettings( + OrgCollectionSetting::AllowAdminAccessToAllCollectionItems as i16, + ), } } // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs @@ -306,10 +274,10 @@ impl Organization { "useApi": true, "hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(), "useResetPassword": CONFIG.mail_enabled(), - "allowAdminAccessToAllCollectionItems": self.collection_settings.allow_admin_access_to_all_collection_items, - "limitCollectionCreation": self.collection_settings.limit_collection_creation, - "limitCollectionDeletion": self.collection_settings.limit_collection_deletion, - "limitItemDeletion": self.collection_settings.limit_item_deletion, + "allowAdminAccessToAllCollectionItems": self.collection_settings.get_flag(OrgCollectionSetting::AllowAdminAccessToAllCollectionItems), + "limitCollectionCreation": self.collection_settings.get_flag(OrgCollectionSetting::LimitCollectionCreation), + "limitCollectionDeletion": self.collection_settings.get_flag(OrgCollectionSetting::LimitCollectionDeletion), + "limitItemDeletion": self.collection_settings.get_flag(OrgCollectionSetting::LimitItemDeletion), "businessName": self.name, "businessAddress1": null, @@ -557,9 +525,9 @@ impl Membership { "accessImportExport": false, "accessReports": false, // If the following 3 Collection roles are set to true a custom user has access all permission - "createNewCollections": membership_type == 4 && self.access_all && !org.collection_settings.limit_collection_creation, + "createNewCollections": membership_type == 4 && self.access_all && !org.collection_settings.get_flag(OrgCollectionSetting::LimitCollectionCreation), "editAnyCollection": membership_type == 4 && self.access_all, - "deleteAnyCollection": membership_type == 4 && self.access_all && !org.collection_settings.limit_collection_deletion, + "deleteAnyCollection": membership_type == 4 && self.access_all && !org.collection_settings.get_flag(OrgCollectionSetting::LimitCollectionCreation), "manageGroups": false, "managePolicies": false, "manageSso": false, // Not supported @@ -612,10 +580,10 @@ impl Membership { "familySponsorshipToDelete": null, "accessSecretsManager": false, - "allowAdminAccessToAllCollectionItems": org.collection_settings.allow_admin_access_to_all_collection_items, - "limitCollectionCreation": org.collection_settings.limit_collection_creation, - "limitCollectionDeletion": org.collection_settings.limit_collection_deletion, - "limitItemDeletion": org.collection_settings.limit_item_deletion, + "allowAdminAccessToAllCollectionItems": org.collection_settings.get_flag(OrgCollectionSetting::AllowAdminAccessToAllCollectionItems), + "limitCollectionCreation": org.collection_settings.get_flag(OrgCollectionSetting::LimitCollectionCreation), + "limitCollectionDeletion": org.collection_settings.get_flag(OrgCollectionSetting::LimitCollectionDeletion), + "limitItemDeletion": org.collection_settings.get_flag(OrgCollectionSetting::LimitItemDeletion), "userIsManagedByOrganization": false, // Means not managed via the Members UI, like SSO "userIsClaimedByOrganization": false, // The new key instead of the obsolete userIsManagedByOrganization