use nodedb_physical::physical_plan::ReturningSpec;
use nodedb_types::RlsWriteCheck;
use crate::error::LiteError;
pub(in crate::query::physical_visitor) fn deny_policy(
op: &str,
returning: Option<&ReturningSpec>,
rls_filters: &[&[u8]],
write_check: &RlsWriteCheck,
) -> Result<(), LiteError> {
if !rls_filters.iter().all(|program| program.is_empty()) || write_gate_applies(write_check) {
return Err(LiteError::Unsupported {
detail: format!(
"{op}: row-level security is enforced by the Origin data plane \
and has no equivalent on the single-node Lite engine"
),
});
}
if returning.is_some() {
return Err(LiteError::Unsupported {
detail: format!("{op}: RETURNING is unsupported on the Lite engine"),
});
}
Ok(())
}
fn write_gate_applies(check: &RlsWriteCheck) -> bool {
match check {
RlsWriteCheck::Predicate(_) => true,
RlsWriteCheck::PendingInjection => true,
RlsWriteCheck::NoPolicyApplies | RlsWriteCheck::SystemInternalCollection => false,
RlsWriteCheck::AlreadyDecidedElsewhere | RlsWriteCheck::DecidedEarlierInRequest => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_write_predicate_is_refused() {
let err = deny_policy(
"KvOp::Insert",
None,
&[],
&RlsWriteCheck::from_injected(vec![1, 2, 3]),
)
.expect_err("a policy Lite cannot evaluate must not admit the write");
assert!(matches!(err, LiteError::Unsupported { .. }));
}
#[test]
fn pending_injection_is_refused() {
let err = deny_policy(
"KvOp::Insert",
None,
&[],
&RlsWriteCheck::pending_injection(),
)
.expect_err("an un-run injection must deny");
assert!(matches!(err, LiteError::Unsupported { .. }));
}
#[test]
fn an_unpoliced_write_is_admitted() {
deny_policy("KvOp::Insert", None, &[], &RlsWriteCheck::NoPolicyApplies)
.expect("no policy applies, so nothing blocks the write");
deny_policy(
"KvOp::Insert",
None,
&[],
&RlsWriteCheck::system_internal_collection(),
)
.expect("a collection no policy attaches to is admitted");
deny_policy(
"KvOp::Insert",
None,
&[],
&RlsWriteCheck::already_decided_elsewhere(),
)
.expect("replay carries a decision already made");
}
#[test]
fn a_read_filter_is_refused() {
let err = deny_policy(
"KvOp::Scan",
None,
&[&[1u8, 2, 3]],
&RlsWriteCheck::NoPolicyApplies,
)
.expect_err("a read-side program must not be dropped");
assert!(matches!(err, LiteError::Unsupported { .. }));
}
}