use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use compact_str::CompactString;
use crate::error::{TraverserError, TraverserResult};
use crate::graph::{
AccessRequest, AccessRequestBuf, Action, AllowAll, Authorizer, GraphStorage, Resource,
};
use crate::traversal::context::is_admin_option;
use crate::traversal::step::GraphStep;
use crate::value::ElementProperty;
#[derive(Clone)]
pub(crate) struct Restrictions {
pub(crate) authorizer: Arc<dyn Authorizer>,
pub(crate) private_graph: bool,
pub(crate) preset_options: Arc<HashMap<CompactString, ElementProperty>>,
pub(crate) required: Vec<(&'static str, AccessRequest<'static>)>,
}
impl Default for Restrictions {
fn default() -> Self {
Self {
authorizer: Arc::new(AllowAll),
private_graph: false,
preset_options: Arc::default(),
required: Vec::new(),
}
}
}
impl std::fmt::Debug for Restrictions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Restrictions")
.field("private_graph", &self.private_graph)
.field("preset_options", &self.preset_options)
.field("required", &self.required)
.finish_non_exhaustive()
}
}
pub(crate) fn permission_denied(
operation: &str,
request: &AccessRequest<'_>,
reason: impl Into<String>,
) -> Box<TraverserError> {
Box::new(TraverserError::PermissionDenied {
step: operation.to_string(),
request: Box::new(request.to_owned_request()),
reason: reason.into(),
})
}
impl Restrictions {
pub(crate) fn authorize(
&self,
operation: &str,
request: &AccessRequest<'_>,
) -> TraverserResult<()> {
if self.private_graph && request.writes_graph() {
return Ok(());
}
self.authorizer
.authorize(request)
.map_err(|denied| permission_denied(operation, request, denied.reason()))
}
#[cfg_attr(not(feature = "script"), allow(dead_code))]
pub(crate) fn authorize_file(
&self,
operation: &str,
action: Action<'_>,
path: &Path,
) -> TraverserResult<PathBuf> {
let request = AccessRequest::new(action, Resource::File { path });
self.authorize(operation, &request)?;
self.authorizer
.resolve_file(path)
.map_err(|denied| permission_denied(operation, &request, denied.reason()))
}
fn authorize_once(
&self,
seen: &mut Vec<AccessRequestBuf>,
operation: &str,
request: &AccessRequest<'_>,
) -> TraverserResult<()> {
if seen.iter().any(|asked| asked.as_request() == *request) {
return Ok(());
}
self.authorize(operation, request)?;
seen.push(request.to_owned_request());
Ok(())
}
pub(crate) fn check<'g, S: GraphStorage + 'g>(
&self,
steps: &mut [GraphStep<'g, S>],
options: &HashMap<CompactString, ElementProperty>,
) -> TraverserResult<()> {
let mut seen = Vec::new();
for (operation, request) in &self.required {
self.authorize_once(&mut seen, operation, request)?;
}
let mut set_by_query: Vec<&CompactString> = options
.iter()
.filter(|(key, value)| {
is_admin_option(key) && self.preset_options.get(*key) != Some(*value)
})
.map(|(key, _)| key)
.collect();
set_by_query.sort();
for key in set_by_query {
self.authorize_once(
&mut seen,
&format!("with(\"{key}\")"),
&AccessRequest::new(Action::Update, Resource::Option { key }),
)?;
}
self.check_steps(steps, &mut seen).map_err(|err| {
GraphStep::number_children(steps);
crate::traversal::source::locate_pre_run_error(steps, None, err, &|step| {
self.check_step(step).is_err()
})
})
}
fn check_step<'g, S: GraphStorage + 'g>(&self, step: &GraphStep<'g, S>) -> TraverserResult<()> {
step.for_each_access(|request| self.authorize(step.kind_name(), &request))
}
fn check_steps<'g, S: GraphStorage + 'g>(
&self,
steps: &mut [GraphStep<'g, S>],
seen: &mut Vec<AccessRequestBuf>,
) -> TraverserResult<()> {
for step in steps {
step.for_each_access(|request| self.authorize_once(seen, step.kind_name(), &request))?;
for child in step.child_traversals_mut() {
self.check_steps(&mut child.steps, seen)?;
}
}
Ok(())
}
}
#[cfg_attr(not(feature = "script"), allow(dead_code))]
pub(crate) fn plan_mutates<'g, S: GraphStorage + 'g>(steps: &mut [GraphStep<'g, S>]) -> bool {
steps.iter_mut().any(|step| {
step.mutates()
|| step
.child_traversals_mut()
.into_iter()
.any(|child| plan_mutates(&mut child.steps))
})
}