use crate::{
ast::{self, EntityType, EntityUID},
parser::Loc,
transitive_closure::TCNode,
};
use educe::Educe;
use std::collections::HashSet;
use super::internal_name_to_entity_type;
use crate::validator::{
partition_nonempty::PartitionNonEmpty,
schema::{AllDefs, SchemaError},
types::Type,
ConditionalName,
};
#[derive(Educe, Clone, Debug)]
#[educe(PartialEq, Eq)]
pub struct ValidatorActionId {
pub(crate) name: EntityUID,
pub(crate) applies_to: ValidatorApplySpec<ast::EntityType>,
pub(crate) descendants: HashSet<EntityUID>,
pub(crate) context: Type,
#[educe(PartialEq(ignore))]
pub(crate) loc: Option<Loc>,
}
impl ValidatorActionId {
pub fn new(
name: EntityUID,
principal_entity_types: impl IntoIterator<Item = ast::EntityType>,
resource_entity_types: impl IntoIterator<Item = ast::EntityType>,
descendants: impl IntoIterator<Item = EntityUID>,
context: Type,
loc: Option<Loc>,
) -> Self {
Self {
name,
applies_to: ValidatorApplySpec::new(
principal_entity_types.into_iter().collect(),
resource_entity_types.into_iter().collect(),
),
descendants: descendants.into_iter().collect(),
context,
loc,
}
}
pub fn name(&self) -> &EntityUID {
&self.name
}
pub fn loc(&self) -> Option<&Loc> {
self.loc.as_ref()
}
pub fn descendants(&self) -> impl Iterator<Item = &EntityUID> {
self.descendants.iter()
}
pub fn context(&self) -> &Type {
&self.context
}
pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
self.applies_to.principal_apply_spec.iter()
}
pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
self.applies_to.resource_apply_spec.iter()
}
pub fn context_type(&self) -> &Type {
&self.context
}
pub fn applies_to_principals(&self) -> impl Iterator<Item = &ast::EntityType> {
self.applies_to.applicable_principal_types()
}
pub fn applies_to_resources(&self) -> impl Iterator<Item = &ast::EntityType> {
self.applies_to.applicable_resource_types()
}
pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
self.applies_to.is_applicable_principal_type(ty)
}
pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
self.applies_to.is_applicable_resource_type(ty)
}
}
impl TCNode<EntityUID> for ValidatorActionId {
fn get_key(&self) -> EntityUID {
self.name.clone()
}
fn add_edge_to(&mut self, k: EntityUID) {
self.descendants.insert(k);
}
fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
Box::new(self.descendants.iter())
}
fn has_edge_to(&self, e: &EntityUID) -> bool {
self.descendants.contains(e)
}
fn reset_edges(&mut self) {}
}
#[derive(Clone, Debug)]
pub(crate) struct ValidatorApplySpec<N> {
principal_apply_spec: HashSet<N>,
resource_apply_spec: HashSet<N>,
}
impl<N: PartialEq + Eq + std::hash::Hash> PartialEq for ValidatorApplySpec<N> {
fn eq(&self, other: &Self) -> bool {
self.principal_apply_spec == other.principal_apply_spec
&& self.resource_apply_spec == other.resource_apply_spec
}
}
impl<N: PartialEq + Eq + std::hash::Hash> Eq for ValidatorApplySpec<N> {}
impl<N> ValidatorApplySpec<N> {
pub fn new(principal_apply_spec: HashSet<N>, resource_apply_spec: HashSet<N>) -> Self {
Self {
principal_apply_spec,
resource_apply_spec,
}
}
}
impl ValidatorApplySpec<ast::EntityType> {
pub fn is_applicable_principal_type(&self, ty: &ast::EntityType) -> bool {
self.principal_apply_spec.contains(ty)
}
pub fn applicable_principal_types(&self) -> impl Iterator<Item = &ast::EntityType> {
self.principal_apply_spec.iter()
}
pub fn is_applicable_resource_type(&self, ty: &ast::EntityType) -> bool {
self.resource_apply_spec.contains(ty)
}
pub fn applicable_resource_types(&self) -> impl Iterator<Item = &ast::EntityType> {
self.resource_apply_spec.iter()
}
}
impl ValidatorApplySpec<ConditionalName> {
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<ValidatorApplySpec<ast::EntityType>, crate::validator::schema::SchemaError> {
let principal_apply_spec = self
.principal_apply_spec
.into_iter()
.map(|cname| {
let internal_name = cname.resolve(all_defs)?;
internal_name_to_entity_type(internal_name).map_err(Into::into)
})
.partition_nonempty();
let resource_apply_spec = self
.resource_apply_spec
.into_iter()
.map(|cname| {
let internal_name = cname.resolve(all_defs)?;
internal_name_to_entity_type(internal_name).map_err(Into::into)
})
.partition_nonempty();
match (principal_apply_spec, resource_apply_spec) {
(Ok(principal_apply_spec), Ok(resource_apply_spec)) => Ok(ValidatorApplySpec {
principal_apply_spec,
resource_apply_spec,
}),
(Ok(_), Err(errs)) => Err(SchemaError::join_nonempty(errs)),
(Err(resource_errs), Ok(_)) => Err(SchemaError::join_nonempty(resource_errs)),
(Err(principal_errs), Err(resource_errs)) => {
let mut errs = principal_errs;
errs.extend(resource_errs);
Err(SchemaError::join_nonempty(errs))
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn make_action() -> ValidatorActionId {
ValidatorActionId {
name: r#"Action::"foo""#.parse().unwrap(),
applies_to: ValidatorApplySpec {
principal_apply_spec: HashSet::from([
"User".parse().unwrap(),
"User".parse().unwrap(),
]),
resource_apply_spec: HashSet::from([
"App".parse().unwrap(),
"File".parse().unwrap(),
]),
},
descendants: HashSet::new(),
context: Type::any_record(),
loc: None,
}
}
#[test]
fn test_resources() {
let a = make_action();
let got = a.resources().cloned().collect::<HashSet<EntityType>>();
let expected = HashSet::from(["App".parse().unwrap(), "File".parse().unwrap()]);
assert_eq!(got, expected);
}
#[test]
fn test_principals() {
let a = make_action();
let got = a.principals().cloned().collect::<Vec<EntityType>>();
let expected: [EntityType; 1] = ["User".parse().unwrap()];
assert_eq!(got, &expected);
}
}