use tokio_postgres::Client;
use super::harness::{Absent, Journal, affected, caller, column, other, refused};
use crate::desired_state::{DesiredState, ExpectedRevision, fixtures};
async fn everything_readable(client: &Client, schema: &str) -> String {
let tables = column(
client,
&format!(
"SELECT tablename FROM pg_tables WHERE schemaname = '{schema}' ORDER BY tablename"
),
)
.await;
assert!(
tables.len() > 10,
"the sweep found {} tables, so it is not sweeping the journal: {tables:?}",
tables.len()
);
let mut rendered = String::new();
for table in tables {
for row in column(client, &format!("SELECT t::text FROM {schema}.{table} t")).await {
rendered.push_str(&table);
rendered.push(' ');
rendered.push_str(&row);
rendered.push('\n');
}
}
for body in column(
client,
&format!(
"SELECT encode(body_inline, 'escape') \
FROM {schema}.axond_cp_resource_version \
WHERE body_inline IS NOT NULL"
),
)
.await
{
rendered.push_str("body ");
rendered.push_str(&body);
rendered.push('\n');
}
rendered
}
async fn rows_of(journal: &Journal, tenant: &str) -> Vec<String> {
let mut rows = Vec::new();
for table in [
"axond_cp_tenant",
"axond_cp_project",
"axond_cp_principal",
"axond_cp_resource_version",
"axond_cp_access_denial",
] {
rows.extend(
journal
.stored(&format!(
"SELECT t::text FROM {table} t WHERE tenant_id = '{tenant}' ORDER BY t::text"
))
.await,
);
}
rows
}
#[tokio::test]
async fn nothing_a_pinned_session_can_read_names_the_other_tenant() {
let Some(journal) = Journal::open().await else {
return;
};
journal.publish_two_tenants().await;
let schema = journal.schema().to_owned();
let pinned = journal.session("reader", "SELECT", Some(caller())).await;
let ours = everything_readable(&pinned, &schema).await;
for (label, id) in [
("its own tenant", caller().to_string()),
("its own project", fixtures::project_id(2).to_string()),
(
"its own administrator",
fixtures::principal_id(31).to_string(),
),
] {
assert!(
ours.contains(&id),
"the pinned session cannot read {label}, so the sweep proves nothing"
);
}
let absent = Absent::of_the_other_tenants_own_rows();
absent.assert_absent("a session pinned to one tenant", &ours);
assert!(
ours.contains(&other().to_string()),
"the journal's deployment-scoped rows no longer name every tenant — if that \
was deliberate, this scenario should now assert the absence instead"
);
let publisher = journal.session("publisher", "SELECT", None).await;
let all = everything_readable(&publisher, &schema).await;
for (label, id) in absent.names() {
assert!(
all.contains(id.as_str()),
"the other tenant's {label} is not stored at all, so hiding it proves nothing"
);
}
}
#[tokio::test]
async fn a_pinned_session_cannot_write_another_tenants_rows() {
let Some(journal) = Journal::open().await else {
return;
};
let revision = journal.publish_two_tenants().await.to_string();
let theirs = other().to_string();
let before = rows_of(&journal, &theirs).await;
let session = journal
.session("writer", "SELECT, INSERT, UPDATE, DELETE", Some(caller()))
.await;
for (attempt, sql) in [
(
"declaring a tenant of its own",
format!(
"INSERT INTO axond_cp_tenant (tenant_id, slug, lifecycle, revision_id) \
VALUES ('{}', 'invented', 'active', '{revision}')",
fixtures::tenant_id(99)
),
),
(
"putting a project in the other tenant",
format!(
"INSERT INTO axond_cp_project (project_id, tenant_id, slug, revision_id) \
VALUES ('{}', '{theirs}', 'seized', '{revision}')",
fixtures::project_id(98)
),
),
(
"putting a principal in the other tenant",
format!(
"INSERT INTO axond_cp_principal (principal_id, resource_id, identity_kind, \
scope_kind, tenant_id, slug, display_name, issuer, subject, revision_id) \
VALUES ('{}', '{}', 'human', 'tenant', '{theirs}', 'planted', 'Planted', \
'https://idp.example', 'planted', '{revision}')",
fixtures::principal_id(97),
fixtures::resource_id(97)
),
),
(
"handing its own project to the other tenant",
format!(
"UPDATE axond_cp_project SET tenant_id = '{theirs}' WHERE project_id = '{}'",
fixtures::project_id(2)
),
),
(
"forging a denial into the other tenant's trail",
format!(
"INSERT INTO axond_cp_access_denial (denial_id, actor_kind, actor_issuer, \
actor_subject, surface, action, scope_kind, tenant_id, reason, recorded_at) \
VALUES ('{}', 'human', 'https://idp.example', 'planted', 'tenant', \
'publish', 'tenant', '{theirs}', 'out-of-scope', now())",
forged_denial_id()
),
),
] {
let error = refused(&session, &sql).await;
assert!(
error.contains("row-level security"),
"{attempt} was refused for the wrong reason: {error}"
);
}
for (attempt, sql) in [
(
"renaming the other tenant's project",
format!("UPDATE axond_cp_project SET slug = 'seized' WHERE tenant_id = '{theirs}'"),
),
(
"disabling the other tenant",
format!(
"UPDATE axond_cp_tenant SET lifecycle = 'disabled' WHERE tenant_id = '{theirs}'"
),
),
(
"deleting the other tenant's principals",
format!("DELETE FROM axond_cp_principal WHERE tenant_id = '{theirs}'"),
),
(
"deleting the other tenant's resources",
format!("DELETE FROM axond_cp_resource_version WHERE tenant_id = '{theirs}'"),
),
] {
assert_eq!(
affected(&session, &sql).await,
0,
"{attempt} affected rows the session should not be able to see"
);
}
let forged = fixtures::resource_id(96).to_string();
assert_eq!(
affected(
&session,
&format!(
"INSERT INTO axond_cp_resource_version (resource_kind, resource_id, version, \
scope_kind, slug, body_form, body_inline, content_checksum, serializer) \
VALUES ('tenant', '{forged}', 1, 'deployment', 'invented', 'inline', '\\x7b7d', \
'sha256:{}', 'json')",
"0".repeat(64)
),
)
.await,
1,
"a deployment-scoped write is refused now — if that was deliberate, this \
scenario should assert the refusal instead"
);
let chained = refused(
&session,
&format!(
"INSERT INTO axond_cp_revision_entry (revision_id, resource_kind, resource_id, version) \
VALUES ('{revision}', 'tenant', '{forged}', 1)"
),
)
.await;
assert!(
chained.contains("row-level security"),
"a pinned session entered a forged version into the publication chain: {chained}"
);
assert!(
journal
.stored("SELECT t::text FROM axond_cp_tenant t WHERE slug = 'invented'")
.await
.is_empty(),
"a deployment-scoped write became a tenant of its own"
);
let after = rows_of(&journal, &theirs).await;
assert_eq!(
after, before,
"the other tenant's durable rows changed under a session pinned elsewhere"
);
assert!(
!before.is_empty(),
"the other tenant has no rows to protect, so this scenario is vacuous"
);
}
fn forged_denial_id() -> String {
fixtures::candidate(ExpectedRevision::Empty, "forge", DesiredState::new())
.audit
.id
.to_string()
}