use nodedb_sql::ddl_ast::TenantSelector;
use crate::control::security::audit::AuditEvent;
use crate::control::security::identity::{AuthenticatedIdentity, Role};
use crate::control::state::SharedState;
use crate::types::TenantId;
use super::super::super::result::{DdlError, DdlResult};
use super::super::auth_support::{parse_role, require_tenant_admin, status};
fn resolve_tenant_selector(
state: &SharedState,
selector: &TenantSelector,
) -> Result<TenantId, DdlError> {
match selector {
TenantSelector::Id(id) => Ok(TenantId::new(*id)),
TenantSelector::Name(name) => {
let catalog = state.credentials.catalog();
let stored = catalog
.find_tenant_by_name(name)
.map_err(|e| DdlError {
sqlstate: "XX000".to_string(),
message: format!("catalog read: {e}"),
})?
.ok_or_else(|| DdlError {
sqlstate: "42704".to_string(),
message: format!("tenant '{name}' not found"),
})?;
Ok(TenantId::new(stored.tenant_id))
}
}
}
pub fn create_user(
state: &SharedState,
identity: &AuthenticatedIdentity,
username: &str,
password: &str,
role_name: Option<&str>,
tenant: Option<&TenantSelector>,
if_not_exists: bool,
) -> Result<Vec<DdlResult>, DdlError> {
require_tenant_admin(identity, "create users")?;
if username.is_empty() {
return Err(DdlError {
sqlstate: "42601".to_string(),
message:
"syntax: CREATE USER <name> WITH PASSWORD '<password>' [ROLE <role>] [TENANT <id>]"
.to_string(),
});
}
if if_not_exists && state.credentials.get_user(username).is_some() {
return Ok(status("CREATE USER"));
}
if password.is_empty() {
return Err(DdlError {
sqlstate: "42601".to_string(),
message: "password must be a single-quoted string".to_string(),
});
}
let role = role_name.map(parse_role).unwrap_or(Role::ReadWrite);
let tenant_id = if let Some(selector) = tenant {
if !identity.is_superuser {
return Err(DdlError {
sqlstate: "42501".to_string(),
message: "only superuser can assign tenants".to_string(),
});
}
resolve_tenant_selector(state, selector)?
} else {
identity.tenant_id
};
let stored = state
.credentials
.prepare_user(username, password, tenant_id, vec![role])
.map_err(|e| DdlError {
sqlstate: "42710".to_string(),
message: e.to_string(),
})?;
let entry = crate::control::catalog_entry::CatalogEntry::PutUser(Box::new(stored.clone()));
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}"),
})?;
if log_index == 0 {
{
let catalog = state.credentials.catalog();
catalog.put_user(&stored).map_err(|e| DdlError {
sqlstate: "XX000".to_string(),
message: format!("catalog write: {e}"),
})?;
}
state.credentials.install_replicated_user(&stored, None);
} else {
if state.credentials.get_user(username).is_none() {
return Err(DdlError {
sqlstate: "40001".to_string(),
message: "transient: metadata entry truncated by leader change, retry".to_string(),
});
}
}
state.audit_record(
AuditEvent::PrivilegeChange,
Some(tenant_id),
&identity.username,
&format!("created user '{username}' in tenant {tenant_id}"),
);
Ok(status("CREATE USER"))
}