use super::*;
use crate::graph::{AccessRequest, Action, AllowAll, Authorizer, Resource};
use crate::traversal::permission_check::Restrictions;
use std::collections::HashMap;
#[derive(Clone)]
pub(crate) struct ScriptAccess {
pub(crate) authorizer: Arc<dyn Authorizer>,
pub(crate) presets: Arc<HashMap<CompactString, ElementProperty>>,
pub(crate) max_value_depth: usize,
pub(crate) read_only: bool,
pub(crate) query_depth: usize,
pub(crate) embedders: Arc<crate::value::Embedders>,
}
impl Default for ScriptAccess {
fn default() -> Self {
Self {
authorizer: Arc::new(AllowAll),
presets: Arc::default(),
max_value_depth: 0,
read_only: false,
query_depth: 0,
embedders: crate::value::Embedders::current(),
}
}
}
impl ScriptAccess {
pub(crate) fn new(
authorizer: Arc<dyn Authorizer>,
options: &[(&str, ElementProperty)],
) -> Self {
Self {
authorizer,
max_value_depth: 0,
read_only: false,
query_depth: 0,
embedders: crate::value::Embedders::current(),
presets: Arc::new(
options
.iter()
.map(|(key, value)| (CompactString::new(key), value.clone()))
.collect(),
),
}
}
pub(crate) fn of_context(context: &NativeCallContext) -> Arc<ScriptAccess> {
context
.tag()
.and_then(|tag| {
tag.read_lock::<EngineAccess>()
.map(|access| access.0.clone())
})
.unwrap_or_default()
}
pub(crate) fn restrictions(&self, private_graph: bool) -> Restrictions {
Restrictions {
authorizer: self.authorizer.clone(),
private_graph,
preset_options: self.presets.clone(),
required: Vec::new(),
}
}
}
pub(crate) fn value_depth_of(context: &NativeCallContext) -> usize {
context
.tag()
.and_then(|tag| {
tag.read_lock::<EngineAccess>()
.map(|access| access.0.max_value_depth)
})
.filter(|depth| *depth > 0)
.unwrap_or(crate::value::depth::DEFAULT_MAX_VALUE_DEPTH)
}
#[derive(Clone)]
pub(crate) struct EngineAccess(pub(crate) Arc<ScriptAccess>, pub(crate) ScriptClock);
#[derive(Clone, Copy, Debug)]
pub(crate) enum ClockSource {
System,
Pinned(crate::value::temporal::DateTime),
}
impl ClockSource {
fn read(self) -> crate::value::temporal::DateTime {
match self {
ClockSource::Pinned(now) => now,
ClockSource::System => {
crate::value::temporal::DateTime::from_micros(crate::graph::undo::now_micros(), 0)
.unwrap_or(crate::value::temporal::DateTime::EPOCH)
}
}
}
}
#[derive(Debug)]
pub(crate) struct ScriptClock {
pub(crate) source: ClockSource,
pub(crate) now: crate::value::temporal::DateTime,
}
impl ScriptClock {
pub(crate) fn new(source: ClockSource) -> Self {
ScriptClock {
source,
now: source.read(),
}
}
pub(crate) fn same_time(&self) -> Self {
ScriptClock::new(ClockSource::Pinned(self.now))
}
}
impl Clone for ScriptClock {
fn clone(&self) -> Self {
ScriptClock::new(self.source)
}
}
pub(crate) fn now_of(context: &NativeCallContext) -> crate::value::temporal::DateTime {
context
.tag()
.and_then(|tag| tag.read_lock::<EngineAccess>().map(|access| access.1.now))
.unwrap_or_else(|| ClockSource::System.read())
}
pub(crate) fn nested_tag(context: &NativeCallContext, access: Arc<ScriptAccess>) -> EngineAccess {
let clock = context
.tag()
.and_then(|tag| tag.read_lock::<EngineAccess>().map(|a| a.1.same_time()))
.unwrap_or_else(|| ScriptClock::new(ClockSource::System));
EngineAccess(access, clock)
}
pub fn pin_now(engine: &mut Engine, now: Option<crate::value::temporal::DateTime>) {
let source = match now {
Some(now) => ClockSource::Pinned(now),
None => ClockSource::System,
};
let access = engine
.default_tag()
.read_lock::<EngineAccess>()
.map(|access| access.0.clone())
.unwrap_or_default();
engine.set_default_tag(Dynamic::from(EngineAccess(
access,
ScriptClock::new(source),
)));
}
pub fn set_engine_embedders(engine: &mut Engine, embedders: Arc<crate::value::Embedders>) {
let (access, clock) = engine
.default_tag()
.read_lock::<EngineAccess>()
.map(|tag| ((*tag.0).clone(), tag.1.clone()))
.unwrap_or_else(|| {
(
ScriptAccess::default(),
ScriptClock::new(ClockSource::System),
)
});
let access = ScriptAccess {
embedders,
..access
};
engine.set_default_tag(Dynamic::from(EngineAccess(Arc::new(access), clock)));
}
pub fn set_scope_embedders(scope: &mut Scope, embedders: Arc<crate::value::Embedders>) {
for name in ["g", "_g", "__"] {
if let Some(value) = scope.get_mut(name)
&& let Some(mut source) = value.write_lock::<ScriptGraphTraversalSource>()
{
let access = ScriptAccess {
embedders: embedders.clone(),
..(*source.access).clone()
};
source.access = Arc::new(access);
}
}
}
pub(crate) fn install(
engine: &mut Engine,
authorizer: Arc<dyn Authorizer>,
options: &[(&str, ElementProperty)],
max_value_depth: usize,
) {
let mut access = ScriptAccess::new(authorizer.clone(), options);
access.max_value_depth = max_value_depth;
engine.set_default_tag(Dynamic::from(EngineAccess(
Arc::new(access),
ScriptClock::new(ClockSource::System),
)));
engine.set_module_resolver(FileImports { authorizer });
}
pub(crate) struct FileImports {
authorizer: Arc<dyn Authorizer>,
}
impl rhai::ModuleResolver for FileImports {
fn resolve(
&self,
engine: &Engine,
source: Option<&str>,
path: &str,
pos: rhai::Position,
) -> Result<rhai::Shared<Module>, Box<EvalAltResult>> {
let file = std::path::Path::new(path).with_extension("rhai");
let restrictions = Restrictions {
authorizer: self.authorizer.clone(),
..Restrictions::default()
};
let resolved = restrictions
.authorize_file("import", Action::Execute, &file)
.map_err(|err| {
Box::new(EvalAltResult::ErrorRuntime(
Dynamic::from(crate::error::format_diagnostic(&err)),
pos,
))
})?;
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
{
let resolver = rhai::module_resolvers::FileModuleResolver::new();
if resolved == file {
resolver.resolve(engine, source, path, pos)
} else {
resolver.resolve(engine, source, &resolved.to_string_lossy(), pos)
}
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
{
let _ = (engine, source, resolved);
Err(Box::new(EvalAltResult::ErrorModuleNotFound(
path.to_string(),
pos,
)))
}
}
}
#[derive(Clone, Default)]
pub(crate) struct GraphOrigin {
access: Arc<ScriptAccess>,
host_graphs: Vec<Arc<dyn AnyGraph>>,
}
impl GraphOrigin {
pub(crate) fn source(&self, graph: Arc<dyn AnyGraph>) -> ScriptGraphTraversalSource {
let private = !self
.host_graphs
.iter()
.any(|host| super::plan::same_graph(host, &graph));
ScriptGraphSource::graph(graph).with_access(self.access.clone(), private)
}
}
impl ScriptGraphTraversalSource {
pub(crate) fn with_access(mut self, access: Arc<ScriptAccess>, private: bool) -> Self {
self.access = access;
self.private = private;
self
}
fn writes_into_host_graph(&self) -> bool {
self.steps.iter().any(|step| {
matches!(step, Step::Source(SourceStep::SideEffectGraph(_, target)) if !target.private)
})
}
pub(crate) fn restrictions(&self) -> Restrictions {
let into_host = self.writes_into_host_graph();
let mut restrictions = self.access.restrictions(self.private && !into_host);
if into_host {
restrictions.required.push((
"with_side_effect",
AccessRequest::new(Action::Create, Resource::data(None, None)),
));
}
restrictions
}
pub(crate) fn require(
&self,
operation: &str,
request: AccessRequest<'_>,
) -> Result<(), Box<EvalAltResult>> {
self.restrictions()
.authorize(operation, &request)
.map_err(script_error)
}
#[cfg(feature = "io")]
pub(crate) fn require_file(
&self,
operation: &str,
action: Action<'_>,
path: &str,
) -> Result<std::path::PathBuf, Box<EvalAltResult>> {
self.restrictions()
.authorize_file(operation, action, std::path::Path::new(path))
.map_err(script_error)
}
pub(crate) fn graph_origin(&self) -> GraphOrigin {
let mut host_graphs: Vec<Arc<dyn AnyGraph>> = self
.steps
.iter()
.filter_map(|step| match step {
Step::Source(SourceStep::SideEffectGraph(_, target)) if !target.private => {
Some(target.graph.clone() as Arc<dyn AnyGraph>)
}
_ => None,
})
.collect();
if !self.private
&& let Some(graph) = &self.g
{
host_graphs.push(graph.clone());
}
GraphOrigin {
access: self.access.clone(),
host_graphs,
}
}
}