Skip to content

Commit 3b95bf2

Browse files
committed
fix(ddl): report tenant-teardown purge counts in the drop-user audit trail
DROP USER during tenant teardown purged the user's owned objects outright but recorded the same "no owned objects required reassignment" audit detail as a user with nothing owned, misreporting a destructive purge as a no-op. purge_owned_for_tenant_teardown now returns the purged count, and the audit detail distinguishes reassignment, no ownership, and teardown purge outcomes.
1 parent 34c9143 commit 3b95bf2

2 files changed

Lines changed: 42 additions & 9 deletions

File tree

nodedb/src/control/server/shared/ddl/neutral/user/drop.rs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ use super::super::auth_support::{require_tenant_admin, status, strip_if_exists};
1919
use super::reassign_owned::reassign_owned_and_sweep_grants;
2020
use super::tenant_purge::purge_owned_for_tenant_teardown;
2121

22+
/// How a dropped user's owned objects were disposed of, for the audit trail.
23+
enum OwnershipDisposition {
24+
/// Objects were reassigned to the named tenant administrator.
25+
Reassigned(String),
26+
/// The user owned nothing that required reassignment.
27+
NoneOwned,
28+
/// Tenant teardown purged the given number of owned objects outright.
29+
Purged(usize),
30+
}
31+
2232
/// DROP USER [IF EXISTS] <name>
2333
pub fn drop_user(
2434
state: &SharedState,
@@ -106,11 +116,22 @@ fn drop_user_inner(
106116
// user, BEFORE removing the user row. Fail-closed: any error here
107117
// aborts the drop, because a partially-reassigned-then-deleted user
108118
// is exactly the dangling-reference bug this guards against.
109-
let admin_name = if tenant_teardown {
110-
purge_owned_for_tenant_teardown(state, username, user_tenant)?;
111-
None
119+
//
120+
// During tenant teardown the owned objects are purged outright (the
121+
// tenant is going away), so the audit trail must record the destructive
122+
// purge — not misreport it as "nothing owned" the way the reassign path's
123+
// `None` does.
124+
let disposition = if tenant_teardown {
125+
OwnershipDisposition::Purged(purge_owned_for_tenant_teardown(
126+
state,
127+
username,
128+
user_tenant,
129+
)?)
112130
} else {
113-
reassign_owned_and_sweep_grants(state, username, user_tenant)?
131+
match reassign_owned_and_sweep_grants(state, username, user_tenant)? {
132+
Some(admin_name) => OwnershipDisposition::Reassigned(admin_name),
133+
None => OwnershipDisposition::NoneOwned,
134+
}
114135
};
115136

116137
// `DropUser` fully removes the identity record on every node —
@@ -145,11 +166,18 @@ fn drop_user_inner(
145166
};
146167

147168
if dropped {
148-
let detail = match admin_name {
149-
Some(admin_name) => {
169+
let detail = match disposition {
170+
OwnershipDisposition::Reassigned(admin_name) => {
150171
format!("dropped user '{username}' (ownership reassigned to '{admin_name}')")
151172
}
152-
None => format!("dropped user '{username}' (no owned objects required reassignment)"),
173+
OwnershipDisposition::NoneOwned => {
174+
format!("dropped user '{username}' (no owned objects required reassignment)")
175+
}
176+
OwnershipDisposition::Purged(purged) => {
177+
format!(
178+
"dropped user '{username}' (tenant teardown purged {purged} owned object(s))"
179+
)
180+
}
153181
};
154182
state.audit_record(
155183
AuditEvent::PrivilegeChange,

nodedb/src/control/server/shared/ddl/neutral/user/tenant_purge.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,20 @@ use crate::types::TenantId;
1111
use super::super::super::result::DdlError;
1212
use super::reassign_owned::{OwnerKind, ddl_err, propose, sweep_grants};
1313

14+
/// Purge every object owned by the tenant administrator during `DROP TENANT`,
15+
/// returning the number of owned objects deleted so the caller can record an
16+
/// accurate audit trail for the destructive teardown.
1417
pub(super) fn purge_owned_for_tenant_teardown(
1518
state: &SharedState,
1619
username: &str,
1720
tenant: TenantId,
18-
) -> Result<(), DdlError> {
21+
) -> Result<usize, DdlError> {
1922
let catalog = state.credentials.catalog();
2023
let mut owned = catalog
2124
.owners_for_user(username, tenant.as_u64())
2225
.map_err(|e| ddl_err(format!("load owner rows: {e}")))?;
2326
owned.sort_by_key(|owner| owner.object_type == object_type::COLLECTION);
27+
let purged = owned.len();
2428

2529
for owner in owned {
2630
let kind = OwnerKind::from_object_type(&owner.object_type).ok_or_else(|| {
@@ -48,7 +52,8 @@ pub(super) fn purge_owned_for_tenant_teardown(
4852
);
4953
}
5054
}
51-
sweep_grants(state, catalog, username)
55+
sweep_grants(state, catalog, username)?;
56+
Ok(purged)
5257
}
5358

5459
fn purge_collection_rls_policies(

0 commit comments

Comments
 (0)