use std::sync::Arc;
use reblessive::tree::Stk;
use crate::catalog::{Permission, TableDefinition};
use crate::ctx::FrozenContext;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::exec::planner::Planner;
use crate::exec::{DatabaseContext, EvalContext, ExecutionContext, PhysicalExpr};
use crate::expr::FlowResultExt as _;
use crate::iam::Action;
use crate::val::Value;
#[derive(Debug, Clone)]
pub enum PhysicalPermission {
Allow,
Deny,
Conditional(Arc<dyn PhysicalExpr>),
}
pub(crate) async fn convert_permission_to_physical(
permission: &Permission,
planner: &Planner<'_>,
) -> Result<PhysicalPermission, Error> {
match permission {
Permission::None => Ok(PhysicalPermission::Deny),
Permission::Full => Ok(PhysicalPermission::Allow),
Permission::Specific(expr) => {
let physical_expr = planner.physical_expr(expr.clone()).await?;
Ok(PhysicalPermission::Conditional(physical_expr))
}
}
}
#[inline]
pub(crate) async fn convert_permission_to_physical_runtime(
permission: &Permission,
ctx: &FrozenContext,
) -> Result<PhysicalPermission, Error> {
convert_permission_to_physical(permission, &Planner::new(ctx)).await
}
pub(crate) fn resolve_select_permission(table_def: Option<&TableDefinition>) -> &Permission {
match table_def {
Some(def) => &def.permissions.select,
None => &Permission::None,
}
}
pub(crate) fn should_check_perms(db_ctx: &DatabaseContext, action: Action) -> Result<bool, Error> {
let root = &db_ctx.ns_ctx.root;
if root.skip_fetch_perms {
return Ok(false);
}
if !root.ctx.auth_enabled() && root.auth.is_anon() {
return Ok(false);
}
let ns = db_ctx.ns_name();
let db = db_ctx.db_name();
match action {
Action::Edit => {
let allowed = root.auth.has_editor_role();
let db_in_actor_level =
root.auth.is_root() || root.auth.is_ns_check(ns) || root.auth.is_db_check(ns, db);
Ok(!allowed || !db_in_actor_level)
}
Action::View => {
let allowed = root.auth.has_viewer_role();
let db_in_actor_level =
root.auth.is_root() || root.auth.is_ns_check(ns) || root.auth.is_db_check(ns, db);
Ok(!allowed || !db_in_actor_level)
}
}
}
pub(crate) fn validate_record_user_access(db_ctx: &DatabaseContext) -> Result<(), Error> {
let root = &db_ctx.ns_ctx.root;
if !root.auth.is_record() {
return Ok(());
}
let ns = db_ctx.ns_name();
let db = db_ctx.db_name();
if root.auth.level().ns() != Some(ns) {
return Err(Error::NsNotAllowed {
ns: ns.into(),
});
}
if root.auth.level().db() != Some(db) {
return Err(Error::DbNotAllowed {
db: db.into(),
});
}
Ok(())
}
pub(crate) async fn check_permission_for_value(
permission: &PhysicalPermission,
value: &Value,
value_param: Option<&Value>,
ctx: &ExecutionContext,
) -> Result<bool, Error> {
match permission {
PhysicalPermission::Deny => Ok(false),
PhysicalPermission::Allow => Ok(true),
PhysicalPermission::Conditional(physical_expr) => {
if ctx.root().skip_fetch_perms {
return Ok(true);
}
let bound_ctx;
let exec_ctx = match value_param {
Some(v) => {
bound_ctx = ctx.with_param("value", v.clone());
&bound_ctx
}
None => ctx,
};
let mut eval_ctx = EvalContext::from_exec_ctx(exec_ctx).with_value(value);
eval_ctx.skip_fetch_perms = true;
let result = physical_expr
.evaluate(eval_ctx)
.await
.map_err(|e| Error::Internal(e.to_string()))?;
Ok(result.is_truthy())
}
}
}
pub(crate) async fn evaluate_table_select_for_doc(
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
permission: &Permission,
cursor_doc: &CursorDoc,
) -> anyhow::Result<bool> {
match permission {
Permission::None => Ok(false),
Permission::Full => Ok(true),
Permission::Specific(e) => {
let opt_no_perms = opt.new_for_permission_predicate();
Ok(stk
.run(|stk| e.compute(stk, ctx, &opt_no_perms, Some(cursor_doc)))
.await
.catch_return()?
.is_truthy())
}
}
}
#[derive(Clone)]
pub(crate) enum CachedTableSelect {
Skip,
Apply(Permission),
Physical(PhysicalPermission, ExecutionContext),
}
pub(crate) async fn resolve_cached_table_select(
ctx: &FrozenContext,
opt: &Options,
table_def: Option<&TableDefinition>,
) -> anyhow::Result<CachedTableSelect> {
if !ctx.check_perms(opt, Action::View)? {
return Ok(CachedTableSelect::Skip);
}
Ok(CachedTableSelect::Apply(resolve_select_permission(table_def).clone()))
}
pub(crate) async fn check_cached_table_select_for_doc(
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
cached: &CachedTableSelect,
cursor_doc: &CursorDoc,
) -> anyhow::Result<bool> {
match cached {
CachedTableSelect::Skip => Ok(true),
CachedTableSelect::Apply(p) => {
evaluate_table_select_for_doc(stk, ctx, opt, p, cursor_doc).await
}
CachedTableSelect::Physical(p, exec_ctx) => {
Ok(check_permission_for_value(p, cursor_doc.doc.as_ref(), None, exec_ctx).await?)
}
}
}
pub(crate) async fn ensure_cached_table_select<'a>(
ctx: &FrozenContext,
opt: &Options,
txn: &crate::kvs::Transaction,
ikb: &crate::idx::IndexKeyBase,
slot: &'a mut Option<CachedTableSelect>,
) -> anyhow::Result<&'a CachedTableSelect> {
use crate::catalog::providers::TableProvider;
if slot.is_none() {
let table = txn.get_tb(ikb.ns(), ikb.db(), ikb.table(), None).await?;
*slot = Some(resolve_cached_table_select(ctx, opt, table.as_deref()).await?);
}
Ok(slot.as_ref().expect("just populated above"))
}