use crate::control::security::audit::AuditEvent;
use crate::control::security::identity::AuthenticatedIdentity;
use crate::control::state::SharedState;
use super::super::super::result::{DdlError, DdlResult};
use super::super::auth_support::{require_tenant_admin, status, strip_if_exists};
use super::reassign_owned::reassign_owned_and_sweep_grants;
use super::tenant_purge::purge_owned_for_tenant_teardown;
enum OwnershipDisposition {
Reassigned(String),
NoneOwned,
Purged(usize),
}
pub fn drop_user(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
drop_user_inner(state, identity, parts, false)
}
pub(in crate::control::server::shared::ddl::neutral) fn drop_tenant_admin(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
drop_user_inner(state, identity, parts, true)
}
fn drop_user_inner(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
tenant_teardown: bool,
) -> Result<Vec<DdlResult>, DdlError> {
require_tenant_admin(identity, "drop users")?;
let (if_exists, parts) = strip_if_exists(parts, 2);
if parts.len() < 3 {
return Err(DdlError {
sqlstate: "42601".to_string(),
message: "syntax: DROP USER [IF EXISTS] <name>".to_string(),
});
}
let username = parts[2];
if username == identity.username {
return Err(DdlError {
sqlstate: "42501".to_string(),
message: "cannot drop your own user".to_string(),
});
}
let user_tenant = state
.credentials
.get_user(username)
.map(|u| u.tenant_id)
.unwrap_or(identity.tenant_id);
let exists_before = state.credentials.get_user(username).is_some();
if !exists_before {
if if_exists {
return Ok(status("DROP USER"));
}
return Err(DdlError {
sqlstate: "42704".to_string(),
message: format!("user '{username}' does not exist"),
});
}
let authoritative_admin = state
.credentials
.catalog()
.authoritative_tenant_admin(user_tenant.as_u64())
.map_err(|e| DdlError {
sqlstate: "XX000".to_string(),
message: format!("load tenant administrator: {e}"),
})?;
if !tenant_teardown && authoritative_admin.as_deref() == Some(username) {
return Err(DdlError {
sqlstate: "55006".to_string(),
message: format!(
"cannot drop user '{username}': it is the authoritative tenant administrator"
),
});
}
let disposition = if tenant_teardown {
OwnershipDisposition::Purged(purge_owned_for_tenant_teardown(
state,
username,
user_tenant,
)?)
} else {
match reassign_owned_and_sweep_grants(state, username, user_tenant)? {
Some(admin_name) => OwnershipDisposition::Reassigned(admin_name),
None => OwnershipDisposition::NoneOwned,
}
};
let entry = crate::control::catalog_entry::CatalogEntry::DropUser {
username: username.to_string(),
};
let log_index = crate::control::metadata_proposer::propose_catalog_entry(state, &entry)
.map_err(|e| DdlError {
sqlstate: "XX000".to_string(),
message: format!("metadata propose: {e}"),
})?;
let dropped = if log_index == 0 {
state
.credentials
.drop_user(username)
.map_err(|e| DdlError {
sqlstate: "XX000".to_string(),
message: e.to_string(),
})?
} else {
true
};
if dropped {
let detail = match disposition {
OwnershipDisposition::Reassigned(admin_name) => {
format!("dropped user '{username}' (ownership reassigned to '{admin_name}')")
}
OwnershipDisposition::NoneOwned => {
format!("dropped user '{username}' (no owned objects required reassignment)")
}
OwnershipDisposition::Purged(purged) => {
format!(
"dropped user '{username}' (tenant teardown purged {purged} owned object(s))"
)
}
};
state.audit_record(
AuditEvent::PrivilegeChange,
Some(identity.tenant_id),
&identity.username,
&detail,
);
Ok(status("DROP USER"))
} else {
Err(DdlError {
sqlstate: "42704".to_string(),
message: format!("user '{username}' does not exist"),
})
}
}