pub(crate) mod authorizer;
pub mod bitvec;
pub(crate) mod compiler;
pub(crate) mod concretizer;
mod decoder;
mod encoder;
pub(crate) mod enforcer;
mod entity_tag;
pub(crate) mod env;
pub mod ext;
pub mod extension_types;
pub(crate) mod extfun;
mod extractor;
pub mod factory;
mod function;
mod interpretation;
pub mod op;
mod result;
mod smtlib_script;
pub mod solver;
pub mod solver_pool;
mod symbolizer;
mod tags;
pub mod term;
pub mod term_type;
#[cfg(test)]
mod test_utils;
pub mod type_abbrevs;
pub mod verifier;
use cedar_policy::Schema;
use cedar_policy_core::ast::{Expr, ExprBuilder, Policy, PolicySet};
use cedar_policy_core::validator::{
typecheck::Typechecker, types::RequestEnv, ValidationMode, Validator,
};
use decoder::{decode_model, IdMaps};
use encoder::Encoder;
use env::to_validator_request_env;
use solver::{Decision, Solver};
use std::borrow::Borrow;
use verifier::Asserts;
use crate::err::{Error, Result};
use crate::solver::DecisionWithModel;
pub use bitvec::BitVecError;
pub use concretizer::ConcretizeError;
pub use concretizer::Env;
pub use decoder::DecodeError;
pub use encoder::EncodeError;
pub use env::{CompiledSchema, Environment, SymEnv};
pub use interpretation::Interpretation;
pub use result::CompileError;
pub use smtlib_script::SmtLibScript;
pub use solver::SolverError;
pub use verifier::{
verify_always_allows, verify_always_denies, verify_always_matches, verify_disjoint,
verify_equivalent, verify_implies, verify_matches_disjoint, verify_matches_equivalent,
verify_matches_implies, verify_never_errors, verify_never_matches,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ResetMode {
#[default]
Emit,
Comment,
}
#[derive(Clone, Debug)]
pub struct SymCompiler<S> {
solver: S,
reset_mode: ResetMode,
}
impl<S> SymCompiler<S> {
pub fn new(solver: S) -> Self {
Self {
solver,
reset_mode: ResetMode::default(),
}
}
pub fn solver(&self) -> &S {
&self.solver
}
pub fn solver_mut(&mut self) -> &mut S {
&mut self.solver
}
pub fn reset_mode(&self) -> ResetMode {
self.reset_mode
}
pub fn set_reset_mode(&mut self, reset_mode: ResetMode) {
self.reset_mode = reset_mode;
}
}
impl<S: Solver> SymCompiler<S> {
async fn emit_reset(&mut self) -> Result<()> {
let script = self.solver.smtlib_input();
match self.reset_mode {
ResetMode::Emit => script.reset().await,
ResetMode::Comment => script.comment("(reset)").await,
}
.map_err(|err| Error::EncodeError(err.into()))
}
pub async fn check_unsat_asserts(
&mut self,
asserts: &Asserts,
symenv: &SymEnv,
) -> Result<bool> {
if asserts.iter().any(|assert| *assert == false.into()) {
Ok(true)
} else if asserts.iter().all(|assert| *assert == true.into()) {
Ok(false)
} else {
self.emit_reset().await?;
self.solver
.smtlib_input()
.set_logic("ALL")
.await
.map_err(|err| Error::EncodeError(err.into()))?;
let mut encoder = Encoder::new(symenv, self.solver.smtlib_input())?;
encoder.encode(asserts.iter()).await?;
match self.solver.check_sat().await? {
Decision::Unsat => Ok(true),
Decision::Sat => Ok(false),
Decision::Unknown => Err(Error::SolverUnknown),
}
}
}
pub async fn check_unsat(
&mut self,
vc: impl FnOnce(&SymEnv) -> std::result::Result<Asserts, CompileError>,
symenv: &SymEnv,
) -> Result<bool> {
let asserts = vc(symenv)?;
self.check_unsat_asserts(&asserts, symenv).await
}
pub async fn check_sat_asserts<'a>(
&mut self,
asserts: &Asserts,
symenv: &'a SymEnv,
) -> Result<Option<Interpretation<'a>>> {
if asserts.iter().any(|assert| *assert == false.into()) {
Ok(None)
} else if asserts.iter().all(|assert| *assert == true.into()) {
Ok(Some(Interpretation::default(symenv)))
} else {
self.emit_reset().await?;
self.solver.enable_models().await?;
self.solver
.smtlib_input()
.set_logic("ALL")
.await
.map_err(|err| Error::EncodeError(err.into()))?;
let mut encoder =
Encoder::new(symenv, self.solver.smtlib_input()).map_err(Error::EncodeError)?;
encoder
.encode(asserts.iter())
.await
.map_err(Error::EncodeError)?;
let encoder = encoder.finalize(); let id_maps = IdMaps::from_encoder(&encoder);
match self.solver.check_sat_with_model().await? {
DecisionWithModel::Unsat => Ok(None),
DecisionWithModel::Sat { model } => {
let interp = decode_model(&model, symenv, &id_maps)?;
#[cfg(debug_assertions)]
{
for assert in asserts.iter() {
if assert.interpret(&interp) != true.into() {
return Err(Error::ModelInvalid {
assert: assert.clone(),
});
}
}
}
Ok(Some(interp))
}
DecisionWithModel::Unknown => Err(Error::SolverUnknown),
}
}
}
pub async fn sat_asserts<E: Borrow<Expr>>(
&mut self,
asserts: &Asserts,
symenv: &SymEnv,
footprint: impl IntoIterator<Item = E>,
) -> Result<Option<Env>> {
match self.check_sat_asserts(asserts, symenv).await? {
None => Ok(None),
Some(interp) => Ok(Some(symenv.extract(footprint, &interp)?)),
}
}
pub async fn check_never_errors(&mut self, policy: &Policy, symenv: &SymEnv) -> Result<bool> {
self.check_unsat(|symenv| verify_never_errors(policy, symenv), symenv)
.await
}
pub async fn check_never_errors_with_counterexample(
&mut self,
policy: &Policy,
symenv: &SymEnv,
) -> Result<Option<Env>> {
self.sat_asserts(
&verify_never_errors(policy, symenv)?,
symenv,
std::iter::once(&policy.condition()),
)
.await
}
pub async fn check_always_matches(&mut self, policy: &Policy, symenv: &SymEnv) -> Result<bool> {
self.check_unsat(|symenv| verify_always_matches(policy, symenv), symenv)
.await
}
pub async fn check_always_matches_with_counterexample(
&mut self,
policy: &Policy,
symenv: &SymEnv,
) -> Result<Option<Env>> {
self.sat_asserts(
&verify_always_matches(policy, symenv)?,
symenv,
std::iter::once(&policy.condition()),
)
.await
}
pub async fn check_never_matches(&mut self, policy: &Policy, symenv: &SymEnv) -> Result<bool> {
self.check_unsat(|symenv| verify_never_matches(policy, symenv), symenv)
.await
}
pub async fn check_never_matches_with_counterexample(
&mut self,
policy: &Policy,
symenv: &SymEnv,
) -> Result<Option<Env>> {
self.sat_asserts(
&verify_never_matches(policy, symenv)?,
symenv,
std::iter::once(&policy.condition()),
)
.await
}
pub async fn check_matches_equivalent(
&mut self,
policy1: &Policy,
policy2: &Policy,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(
|symenv| verify_matches_equivalent(policy1, policy2, symenv),
symenv,
)
.await
}
pub async fn check_matches_equivalent_with_counterexample(
&mut self,
policy1: &Policy,
policy2: &Policy,
symenv: &SymEnv,
) -> Result<Option<Env>> {
self.sat_asserts(
&verify_matches_equivalent(policy1, policy2, symenv)?,
symenv,
[&policy1.condition(), &policy2.condition()],
)
.await
}
pub async fn check_matches_implies(
&mut self,
policy1: &Policy,
policy2: &Policy,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(
|symenv| verify_matches_implies(policy1, policy2, symenv),
symenv,
)
.await
}
pub async fn check_matches_implies_with_counterexample(
&mut self,
policy1: &Policy,
policy2: &Policy,
symenv: &SymEnv,
) -> Result<Option<Env>> {
self.sat_asserts(
&verify_matches_implies(policy1, policy2, symenv)?,
symenv,
[&policy1.condition(), &policy2.condition()],
)
.await
}
pub async fn check_matches_disjoint(
&mut self,
policy1: &Policy,
policy2: &Policy,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(
|symenv| verify_matches_disjoint(policy1, policy2, symenv),
symenv,
)
.await
}
pub async fn check_matches_disjoint_with_counterexample(
&mut self,
policy1: &Policy,
policy2: &Policy,
symenv: &SymEnv,
) -> Result<Option<Env>> {
self.sat_asserts(
&verify_matches_disjoint(policy1, policy2, symenv)?,
symenv,
[&policy1.condition(), &policy2.condition()],
)
.await
}
pub async fn check_implies(
&mut self,
policies1: &PolicySet,
policies2: &PolicySet,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(
|symenv| verify_implies(policies1, policies2, symenv),
symenv,
)
.await
}
pub async fn check_implies_with_counterexample(
&mut self,
policies1: &PolicySet,
policies2: &PolicySet,
symenv: &SymEnv,
) -> Result<Option<Env>> {
let footprint = policies1
.policies()
.chain(policies2.policies())
.map(|p| p.condition());
self.sat_asserts(
&verify_implies(policies1, policies2, symenv)?,
symenv,
footprint,
)
.await
}
pub async fn check_always_allows(
&mut self,
policies: &PolicySet,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(|symenv| verify_always_allows(policies, symenv), symenv)
.await
}
pub async fn check_always_allows_with_counterexample(
&mut self,
policies: &PolicySet,
symenv: &SymEnv,
) -> Result<Option<Env>> {
let footprint = policies
.policies()
.map(|p| p.condition())
.collect::<Vec<_>>();
self.sat_asserts(
&verify_always_allows(policies, symenv)?,
symenv,
footprint.iter(),
)
.await
}
pub async fn check_always_denies(
&mut self,
policies: &PolicySet,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(|symenv| verify_always_denies(policies, symenv), symenv)
.await
}
pub async fn check_always_denies_with_counterexample(
&mut self,
policies: &PolicySet,
symenv: &SymEnv,
) -> Result<Option<Env>> {
let footprint = policies
.policies()
.map(|p| p.condition())
.collect::<Vec<_>>();
self.sat_asserts(
&verify_always_denies(policies, symenv)?,
symenv,
footprint.iter(),
)
.await
}
pub async fn check_equivalent(
&mut self,
policies1: &PolicySet,
policies2: &PolicySet,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(
|symenv| verify_equivalent(policies1, policies2, symenv),
symenv,
)
.await
}
pub async fn check_equivalent_with_counterexample(
&mut self,
policies1: &PolicySet,
policies2: &PolicySet,
symenv: &SymEnv,
) -> Result<Option<Env>> {
let footprint = policies1
.policies()
.chain(policies2.policies())
.map(|p| p.condition())
.collect::<Vec<_>>();
self.sat_asserts(
&verify_equivalent(policies1, policies2, symenv)?,
symenv,
footprint.iter(),
)
.await
}
pub async fn check_disjoint(
&mut self,
policies1: &PolicySet,
policies2: &PolicySet,
symenv: &SymEnv,
) -> Result<bool> {
self.check_unsat(
|symenv| verify_disjoint(policies1, policies2, symenv),
symenv,
)
.await
}
pub async fn check_disjoint_with_counterexample(
&mut self,
policies1: &PolicySet,
policies2: &PolicySet,
symenv: &SymEnv,
) -> Result<Option<Env>> {
let footprint = policies1
.policies()
.chain(policies2.policies())
.map(|p| p.condition())
.collect::<Vec<_>>();
self.sat_asserts(
&verify_disjoint(policies1, policies2, symenv)?,
symenv,
footprint.iter(),
)
.await
}
}
pub fn well_typed_policy(
policy: &Policy,
env: &cedar_policy::RequestEnv,
schema: &Schema,
) -> Result<Policy> {
let env = to_validator_request_env(env, schema.as_ref())
.ok_or_else(|| Error::ActionNotInSchema(env.action().to_string()))?;
well_typed_policy_inner(policy, &env, schema)
}
fn well_typed_policy_inner(
policy: &Policy,
env: &RequestEnv<'_>,
schema: &Schema,
) -> Result<Policy> {
let validator_schema = schema.as_ref();
let errs: Vec<_> =
Validator::validate_entity_types_and_literals(schema.as_ref(), policy.template()).collect();
if !errs.is_empty() {
return Err(Error::PolicyNotWellTyped { errs });
}
let type_checker = Typechecker::new(validator_schema, ValidationMode::Strict);
let policy_check = type_checker.typecheck_by_single_request_env(policy.template(), env);
use cedar_policy_core::validator::typecheck::PolicyCheck::*;
match policy_check {
Success(expr) => Ok(Policy::from_when_clause(
policy.effect(),
expr.into_expr::<ExprBuilder<()>>(),
policy.id().clone(),
policy.loc().cloned(),
)),
Irrelevant(errs, expr) =>
{
if errs.is_empty() {
Ok(Policy::from_when_clause(
policy.effect(),
expr.into_expr::<ExprBuilder<()>>(),
policy.id().clone(),
policy.loc().cloned(),
))
} else {
Err(Error::PolicyNotWellTyped { errs })
}
}
Fail(errs) => Err(Error::PolicyNotWellTyped { errs }),
}
}
pub fn well_typed_policies(
policies: &PolicySet,
env: &cedar_policy::RequestEnv,
schema: &Schema,
) -> Result<PolicySet> {
if policies.policies().any(|p| !p.is_static()) {
return Err(CompileError::UnsupportedFeature(
"template-linked policies are not supported".to_string(),
)
.into());
}
let env = to_validator_request_env(env, schema.as_ref())
.ok_or_else(|| Error::ActionNotInSchema(env.action().to_string()))?;
let typed_policies: Result<Vec<Policy>> = policies
.static_policies()
.map(|p| well_typed_policy_inner(p, &env, schema))
.collect();
match typed_policies {
Ok(ps) => {
let mut res = PolicySet::new();
#[expect(
clippy::unwrap_used,
reason = "adding well-typed policy should not error"
)]
ps.into_iter().for_each(|p| res.add(p).unwrap());
Ok(res)
}
Err(err) => Err(err),
}
}