Browse Source

feat: add collection management settings and persistance

TODO: implement security checks
pull/7452/head
Raphaël Roumezin 1 day ago
parent
commit
521669ec8a
  1. 4
      migrations/mysql/2026-07-21-120000_add_collection_management/down.sql
  2. 4
      migrations/mysql/2026-07-21-120000_add_collection_management/up.sql
  3. 4
      migrations/postgresql/2026-07-21-120000_add_collection_management/down.sql
  4. 4
      migrations/postgresql/2026-07-21-120000_add_collection_management/up.sql
  5. 4
      migrations/sqlite/2026-07-21-120000_add_collection_management/down.sql
  6. 4
      migrations/sqlite/2026-07-21-120000_add_collection_management/up.sql
  7. 56
      src/api/core/organizations.rs
  8. 27
      src/db/models/organization.rs
  9. 4
      src/db/schema.rs
  10. 5
      src/static/templates/scss/vaultwarden.scss.hbs

4
migrations/mysql/2026-07-21-120000_add_collection_management/down.sql

@ -0,0 +1,4 @@
ALTER TABLE organizations DROP COLUMN allow_admin_access_to_all_collection_items;
ALTER TABLE organizations DROP COLUMN limit_collection_creation;
ALTER TABLE organizations DROP COLUMN limit_collection_deletion;
ALTER TABLE organizations DROP COLUMN limit_item_deletion;

4
migrations/mysql/2026-07-21-120000_add_collection_management/up.sql

@ -0,0 +1,4 @@
ALTER TABLE organizations ADD COLUMN allow_admin_access_to_all_collection_items BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE organizations ADD COLUMN limit_collection_creation BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE organizations ADD COLUMN limit_collection_deletion BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE organizations ADD COLUMN limit_item_deletion BOOLEAN NOT NULL DEFAULT FALSE;

4
migrations/postgresql/2026-07-21-120000_add_collection_management/down.sql

@ -0,0 +1,4 @@
ALTER TABLE organizations DROP COLUMN allow_admin_access_to_all_collection_items;
ALTER TABLE organizations DROP COLUMN limit_collection_creation;
ALTER TABLE organizations DROP COLUMN limit_collection_deletion;
ALTER TABLE organizations DROP COLUMN limit_item_deletion;

4
migrations/postgresql/2026-07-21-120000_add_collection_management/up.sql

@ -0,0 +1,4 @@
ALTER TABLE organizations ADD COLUMN allow_admin_access_to_all_collection_items BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE organizations ADD COLUMN limit_collection_creation BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE organizations ADD COLUMN limit_collection_deletion BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE organizations ADD COLUMN limit_item_deletion BOOLEAN NOT NULL DEFAULT FALSE;

4
migrations/sqlite/2026-07-21-120000_add_collection_management/down.sql

@ -0,0 +1,4 @@
ALTER TABLE organizations DROP COLUMN allow_admin_access_to_all_collection_items;
ALTER TABLE organizations DROP COLUMN limit_collection_creation;
ALTER TABLE organizations DROP COLUMN limit_collection_deletion;
ALTER TABLE organizations DROP COLUMN limit_item_deletion;

4
migrations/sqlite/2026-07-21-120000_add_collection_management/up.sql

@ -0,0 +1,4 @@
ALTER TABLE organizations ADD COLUMN allow_admin_access_to_all_collection_items BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE organizations ADD COLUMN limit_collection_creation BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE organizations ADD COLUMN limit_collection_deletion BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE organizations ADD COLUMN limit_item_deletion BOOLEAN NOT NULL DEFAULT FALSE;

56
src/api/core/organizations.rs

@ -39,6 +39,7 @@ pub fn routes() -> Vec<Route> {
get_collection_users,
put_organization,
post_organization,
put_organization_collection_management,
post_organization_collections,
post_bulk_access_collections,
post_organization_collection_update,
@ -126,6 +127,15 @@ struct OrganizationUpdateData {
name: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct OrganizationCollectionManagementUpdateData {
allow_admin_access_to_all_collection_items: Option<bool>,
limit_collection_creation: Option<bool>,
limit_collection_deletion: Option<bool>,
limit_item_deletion: Option<bool>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct FullCollectionData {
@ -340,6 +350,52 @@ async fn post_organization(
Ok(Json(org.to_json()))
}
#[put("/organizations/<org_id>/collection-management", data = "<data>")]
async fn put_organization_collection_management(
org_id: OrganizationId,
headers: OwnerHeaders,
data: Json<OrganizationCollectionManagementUpdateData>,
conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: OrganizationCollectionManagementUpdateData = data.into_inner();
let Some(mut org) = Organization::find_by_uuid(&org_id, &conn).await else {
err!("Organization not found")
};
if let Some(flag) = data.allow_admin_access_to_all_collection_items {
org.allow_admin_access_to_all_collection_items = flag;
};
if let Some(flag) = data.limit_collection_creation {
org.limit_collection_creation = flag;
};
if let Some(flag) = data.limit_collection_deletion {
org.limit_collection_deletion = flag;
};
if let Some(flag) = data.limit_item_deletion {
org.limit_item_deletion = flag;
};
org.save(&conn).await?;
log_event(
EventType::OrganizationUpdated as i32,
org_id.as_ref(),
&org_id,
&headers.user.uuid,
headers.device.atype,
&headers.ip.ip,
&conn,
)
.await;
Ok(Json(org.to_json()))
}
// GET /api/collections?writeOnly=false
#[get("/collections")]
async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {

27
src/db/models/organization.rs

@ -38,6 +38,11 @@ pub struct Organization {
pub billing_email: String,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub allow_admin_access_to_all_collection_items: bool,
pub limit_collection_creation: bool,
pub limit_collection_deletion: bool,
pub limit_item_deletion: bool,
}
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -193,6 +198,10 @@ impl Organization {
billing_email,
private_key,
public_key,
allow_admin_access_to_all_collection_items: true,
limit_collection_creation: false,
limit_collection_deletion: false,
limit_item_deletion: false,
}
}
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs
@ -219,9 +228,10 @@ impl Organization {
"useApi": true,
"hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
"useResetPassword": CONFIG.mail_enabled(),
"allowAdminAccessToAllCollectionItems": true,
"limitCollectionCreation": true,
"limitCollectionDeletion": true,
"allowAdminAccessToAllCollectionItems": self.allow_admin_access_to_all_collection_items,
"limitCollectionCreation": self.limit_collection_creation,
"limitCollectionDeletion": self.limit_collection_deletion,
"limitItemDeletion": self.limit_item_deletion,
"businessName": self.name,
"businessAddress1": null,
@ -509,11 +519,12 @@ impl Membership {
"familySponsorshipValidUntil": null,
"familySponsorshipToDelete": null,
"accessSecretsManager": false,
// limit collection creation to managers with access_all permission to prevent issues
"limitCollectionCreation": self.atype < MembershipType::Manager || !self.access_all,
"limitCollectionDeletion": true,
"limitItemDeletion": false,
"allowAdminAccessToAllCollectionItems": true,
"allowAdminAccessToAllCollectionItems": org.allow_admin_access_to_all_collection_items,
"limitCollectionCreation": org.limit_collection_creation,
"limitCollectionDeletion": org.limit_collection_deletion,
"limitItemDeletion": org.limit_item_deletion,
"userIsManagedByOrganization": false, // Means not managed via the Members UI, like SSO
"userIsClaimedByOrganization": false, // The new key instead of the obsolete userIsManagedByOrganization

4
src/db/schema.rs

@ -126,6 +126,10 @@ table! {
billing_email -> Text,
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
allow_admin_access_to_all_collection_items -> Bool,
limit_collection_creation -> Bool,
limit_collection_deletion -> Bool,
limit_item_deletion -> Bool,
}
}

5
src/static/templates/scss/vaultwarden.scss.hbs

@ -100,11 +100,6 @@ app-organization-plans > form > bit-section:nth-child(2) {
@extend %vw-hide;
}
/* Hide Collection Management Form */
app-org-account form.ng-untouched:nth-child(5) {
@extend %vw-hide;
}
/* Hide 'Member Access' Report Card from Org Reports */
app-org-reports-home > app-report-list > div.tw-inline-grid > div:nth-child(6) {
@extend %vw-hide;

Loading…
Cancel
Save