use crate::audit::AuditScope; use kanidm_proto::v1::Modify as ProtoModify; use kanidm_proto::v1::ModifyList as ProtoModifyList; use crate::schema::SchemaTransaction; use crate::server::{QueryServerTransaction, QueryServerWriteTransaction}; use crate::value::{PartialValue, Value}; use kanidm_proto::v1::{OperationError, SchemaError}; // Should this be std? use std::slice; #[derive(Serialize, Deserialize, Debug)] pub struct ModifyValid; #[derive(Serialize, Deserialize, Debug)] pub struct ModifyInvalid; #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum Modify { // This value *should* exist. // Clippy doesn't like value here, as value > pv. It could be an improvement to // box here, but not sure. ... TODO and thought needed. Present(String, Value), // This value *should not* exist. Removed(String, PartialValue), // This attr *should not* exist. Purged(String), } #[allow(dead_code)] pub fn m_pres(a: &str, v: &Value) -> Modify { Modify::Present(a.to_string(), v.clone()) } #[allow(dead_code)] pub fn m_remove(a: &str, v: &PartialValue) -> Modify { Modify::Removed(a.to_string(), v.clone()) } #[allow(dead_code)] pub fn m_purge(a: &str) -> Modify { Modify::Purged(a.to_string()) } impl Modify { pub fn from( audit: &mut AuditScope, m: &ProtoModify, qs: &mut QueryServerWriteTransaction, ) -> Result { Ok(match m { ProtoModify::Present(a, v) => Modify::Present(a.clone(), qs.clone_value(audit, a, v)?), ProtoModify::Removed(a, v) => { Modify::Removed(a.clone(), qs.clone_partialvalue(audit, a, v)?) } ProtoModify::Purged(a) => Modify::Purged(a.clone()), }) } } #[derive(Debug, Default)] pub struct ModifyList { valid: VALID, // The order of this list matters. Each change must be done in order. mods: Vec, } impl<'a> IntoIterator for &'a ModifyList { type Item = &'a Modify; type IntoIter = slice::Iter<'a, Modify>; fn into_iter(self) -> Self::IntoIter { self.mods.iter() } } impl ModifyList { pub fn new() -> Self { ModifyList { valid: ModifyInvalid, mods: Vec::new(), } } pub fn new_list(mods: Vec) -> Self { ModifyList { valid: ModifyInvalid, mods, } } pub fn new_purge_and_set(attr: &str, v: Value) -> Self { Self::new_list(vec![m_purge(attr), Modify::Present(attr.to_string(), v)]) } pub fn new_append(attr: &str, v: Value) -> Self { Self::new_list(vec![Modify::Present(attr.to_string(), v)]) } pub fn new_purge(attr: &str) -> Self { Self::new_list(vec![m_purge(attr)]) } pub fn push_mod(&mut self, modify: Modify) { self.mods.push(modify) } pub fn from( audit: &mut AuditScope, ml: &ProtoModifyList, qs: &mut QueryServerWriteTransaction, ) -> Result { // For each ProtoModify, do a from. let inner: Result, _> = ml .mods .iter() .map(|pm| Modify::from(audit, pm, qs)) .collect(); match inner { Ok(m) => Ok(ModifyList { valid: ModifyInvalid, mods: m, }), Err(e) => Err(e), } } pub fn validate( &self, schema: &dyn SchemaTransaction, ) -> Result, SchemaError> { let schema_attributes = schema.get_attributes(); /* let schema_name = schema_attributes .get("name") .expect("Critical: Core schema corrupt or missing. To initiate a core transfer, please deposit substitute core in receptacle."); */ let res: Result, _> = (&self.mods) .iter() .map(|m| match m { Modify::Present(attr, value) => { let attr_norm = schema.normalise_attr_name(attr); match schema_attributes.get(&attr_norm) { Some(schema_a) => schema_a .validate_value(attr_norm.as_str(), &value) .map(|_| Modify::Present(attr_norm, value.clone())), None => Err(SchemaError::InvalidAttribute(attr_norm)), } } Modify::Removed(attr, value) => { let attr_norm = schema.normalise_attr_name(attr); match schema_attributes.get(&attr_norm) { Some(schema_a) => schema_a .validate_partialvalue(attr_norm.as_str(), &value) .map(|_| Modify::Removed(attr_norm, value.clone())), None => Err(SchemaError::InvalidAttribute(attr_norm)), } } Modify::Purged(attr) => { let attr_norm = schema.normalise_attr_name(attr); match schema_attributes.get(&attr_norm) { Some(_attr_name) => Ok(Modify::Purged(attr_norm)), None => Err(SchemaError::InvalidAttribute(attr_norm)), } } }) .collect(); let valid_mods = match res { Ok(v) => v, Err(e) => return Err(e), }; // Return new ModifyList! Ok(ModifyList { valid: ModifyValid, mods: valid_mods, }) } #[cfg(test)] pub unsafe fn into_valid(self) -> ModifyList { ModifyList { valid: ModifyValid, mods: self.mods, } } } impl ModifyList { #[cfg(test)] pub unsafe fn new_valid_list(mods: Vec) -> Self { ModifyList { valid: ModifyValid, mods, } } pub fn iter(&self) -> slice::Iter { self.mods.iter() } } impl ModifyList { pub fn len(&self) -> usize { self.mods.len() } }