use serde_json::{Map, Value as JsonValue};
use crate::control::security::identity::AuthenticatedIdentity;
use crate::control::server::response_shape::types::{DdlColType, ShapedRows};
use crate::control::state::SharedState;
use super::super::result::{DdlError, DdlResult};
fn ddl_err(sqlstate: &str, message: impl Into<String>) -> DdlError {
DdlError {
sqlstate: sqlstate.to_string(),
message: message.into(),
}
}
fn audit_columns() -> (Vec<String>, Vec<DdlColType>) {
(
vec![
"seq".to_string(),
"timestamp_us".to_string(),
"event".to_string(),
"tenant_id".to_string(),
"database_id".to_string(),
"source".to_string(),
"detail".to_string(),
],
vec![
DdlColType::Int8,
DdlColType::Int8,
DdlColType::Text,
DdlColType::Int8,
DdlColType::Int8,
DdlColType::Text,
DdlColType::Text,
],
)
}
pub fn show_audit_log(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err(
"42501",
"permission denied: only superuser can view audit log",
));
}
let limit = if parts.len() >= 5 && parts[3].eq_ignore_ascii_case("LIMIT") {
parts[4].parse::<usize>().unwrap_or(100)
} else {
100
};
let catalog = state.credentials.catalog();
let entries = catalog
.load_recent_audit_entries(limit)
.map_err(|e| ddl_err("XX000", e.to_string()))?;
let (columns, column_types) = audit_columns();
let mut rows = Vec::with_capacity(entries.len());
for entry in entries.iter().rev() {
let mut row = Map::new();
row.insert(
"seq".to_string(),
JsonValue::String((entry.seq as i64).to_string()),
);
row.insert(
"timestamp_us".to_string(),
JsonValue::String((entry.timestamp_us as i64).to_string()),
);
row.insert("event".to_string(), JsonValue::String(entry.event.clone()));
row.insert(
"tenant_id".to_string(),
JsonValue::String((entry.tenant_id.unwrap_or(0) as i64).to_string()),
);
row.insert(
"database_id".to_string(),
JsonValue::String((entry.database_id.unwrap_or(0) as i64).to_string()),
);
row.insert(
"source".to_string(),
JsonValue::String(entry.source.clone()),
);
row.insert(
"detail".to_string(),
JsonValue::String(entry.detail.clone()),
);
rows.push(row);
}
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types,
rows,
notice: None,
})])
}
pub fn show_audit_where(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err(
"42501",
"permission denied: only superuser can view audit log",
));
}
let event_filter = if parts.len() >= 6 && parts[3].eq_ignore_ascii_case("event_type") {
parts[5].trim_matches('\'').to_ascii_lowercase()
} else {
return Err(ddl_err(
"42601",
"syntax: SHOW AUDIT WHERE event_type = '<event_name>' [LIMIT <n>]",
));
};
let limit = if parts.len() >= 8 && parts[6].eq_ignore_ascii_case("LIMIT") {
parts[7].parse::<usize>().map_err(|_| {
ddl_err(
"42601",
"syntax: SHOW AUDIT WHERE event_type = '<event_name>' [LIMIT <n>] (LIMIT must be a non-negative integer)",
)
})?
} else {
100
};
let log = match state.audit.lock() {
Ok(l) => l,
Err(p) => p.into_inner(),
};
let (columns, column_types) = audit_columns();
let all = log.all();
let mut rows = Vec::new();
for entry in all.iter().rev() {
if rows.len() >= limit {
break;
}
if entry.event.snake_name() != event_filter {
continue;
}
let mut row = Map::new();
row.insert(
"seq".to_string(),
JsonValue::String((entry.seq as i64).to_string()),
);
row.insert(
"timestamp_us".to_string(),
JsonValue::String((entry.timestamp_us as i64).to_string()),
);
row.insert(
"event".to_string(),
JsonValue::String(entry.event.snake_name().to_string()),
);
row.insert(
"tenant_id".to_string(),
JsonValue::String((entry.tenant_id.map_or(0i64, |t| t.as_u64() as i64)).to_string()),
);
row.insert(
"database_id".to_string(),
JsonValue::String((entry.database_id.map_or(0i64, |d| d.as_u64() as i64)).to_string()),
);
row.insert(
"source".to_string(),
JsonValue::String(entry.source.clone()),
);
row.insert(
"detail".to_string(),
JsonValue::String(entry.detail.clone()),
);
rows.push(row);
}
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types,
rows,
notice: None,
})])
}
pub fn show_audit_in_database(
state: &SharedState,
identity: &AuthenticatedIdentity,
db_name: &str,
limit: usize,
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err(
"42501",
"permission denied: only superuser can view audit log",
));
}
let catalog = state.credentials.catalog();
let db_id = catalog
.get_database_id_by_name(db_name)
.map_err(|e| ddl_err("XX000", format!("catalog lookup failed: {e}")))?
.ok_or_else(|| ddl_err("3D000", format!("database '{db_name}' does not exist")))?;
let (columns, column_types) = audit_columns();
let mut rows = Vec::new();
let log = match state.audit.lock() {
Ok(l) => l,
Err(p) => p.into_inner(),
};
for entry in log.query_by_database(db_id).into_iter().rev() {
if rows.len() >= limit {
break;
}
let mut row = Map::new();
row.insert(
"seq".to_string(),
JsonValue::String((entry.seq as i64).to_string()),
);
row.insert(
"timestamp_us".to_string(),
JsonValue::String((entry.timestamp_us as i64).to_string()),
);
row.insert(
"event".to_string(),
JsonValue::String(entry.event.snake_name().to_string()),
);
row.insert(
"tenant_id".to_string(),
JsonValue::String((entry.tenant_id.map_or(0i64, |t| t.as_u64() as i64)).to_string()),
);
row.insert(
"database_id".to_string(),
JsonValue::String((entry.database_id.map_or(0i64, |d| d.as_u64() as i64)).to_string()),
);
row.insert(
"source".to_string(),
JsonValue::String(entry.source.clone()),
);
row.insert(
"detail".to_string(),
JsonValue::String(entry.detail.clone()),
);
rows.push(row);
}
drop(log);
if rows.len() < limit {
let remaining = limit - rows.len();
let all_entries = catalog
.load_recent_audit_entries(remaining * 10)
.map_err(|e| ddl_err("XX000", e.to_string()))?;
for entry in all_entries.iter().rev() {
if rows.len() >= limit {
break;
}
if entry.database_id != Some(db_id.as_u64()) {
continue;
}
let mut row = Map::new();
row.insert(
"seq".to_string(),
JsonValue::String((entry.seq as i64).to_string()),
);
row.insert(
"timestamp_us".to_string(),
JsonValue::String((entry.timestamp_us as i64).to_string()),
);
row.insert("event".to_string(), JsonValue::String(entry.event.clone()));
row.insert(
"tenant_id".to_string(),
JsonValue::String((entry.tenant_id.unwrap_or(0) as i64).to_string()),
);
row.insert(
"database_id".to_string(),
JsonValue::String((entry.database_id.unwrap_or(0) as i64).to_string()),
);
row.insert(
"source".to_string(),
JsonValue::String(entry.source.clone()),
);
row.insert(
"detail".to_string(),
JsonValue::String(entry.detail.clone()),
);
rows.push(row);
}
}
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types,
rows,
notice: None,
})])
}
pub fn export_audit_log(
_state: &SharedState,
identity: &AuthenticatedIdentity,
_parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(ddl_err(
"42501",
"permission denied: only superuser can export audit log",
));
}
Err(ddl_err(
"0A000",
"use `SELECT ... FROM system.audit_log` and redirect the query \
result on the client",
))
}