kanidmd_lib/
modify.rs

1//! Modification expressions and validation. This is how `ModifyEvents` store and
2//! express the series of Modifications that should be applied. These are expressed
3//! as "states" on what attribute-values should appear as within the `Entry`
4
5use std::slice;
6
7use kanidm_proto::internal::{
8    Modify as ProtoModify, ModifyList as ProtoModifyList, OperationError, SchemaError,
9};
10use kanidm_proto::v1::Entry as ProtoEntry;
11// Should this be std?
12use serde::{Deserialize, Serialize};
13use std::collections::BTreeMap;
14
15use crate::prelude::*;
16use crate::schema::SchemaTransaction;
17use crate::value::{PartialValue, Value};
18
19#[derive(Serialize, Deserialize, Debug, Clone)]
20pub struct ModifyValid;
21#[derive(Serialize, Deserialize, Debug, Clone)]
22pub struct ModifyInvalid;
23
24#[derive(Debug, Clone)]
25#[allow(clippy::large_enum_variant)]
26pub enum Modify {
27    /// This value *should* exist for this attribute.
28    Present(Attribute, Value),
29    /// This value *should not* exist for this attribute.
30    Removed(Attribute, PartialValue),
31    /// This attr should not exist, and if it does exist, will have all content removed.
32    Purged(Attribute),
33    /// This attr and value must exist *in this state* for this change to proceed.
34    Assert(Attribute, PartialValue),
35    /// Set and replace the entire content of an attribute. This requires both presence
36    /// and removal access to the attribute to proceed.
37    Set(Attribute, ValueSet),
38}
39
40pub fn m_pres(attr: Attribute, v: &Value) -> Modify {
41    Modify::Present(attr, v.clone())
42}
43
44pub fn m_remove(attr: Attribute, v: &PartialValue) -> Modify {
45    Modify::Removed(attr, v.clone())
46}
47
48pub fn m_purge(attr: Attribute) -> Modify {
49    Modify::Purged(attr)
50}
51
52pub fn m_assert(attr: Attribute, v: &PartialValue) -> Modify {
53    Modify::Assert(attr, v.clone())
54}
55
56impl Modify {
57    pub fn from(
58        m: &ProtoModify,
59        qs: &mut QueryServerWriteTransaction,
60    ) -> Result<Self, OperationError> {
61        Ok(match m {
62            ProtoModify::Present(a, v) => {
63                let a = Attribute::from(a.as_str());
64                let v = qs.clone_value(&a, v)?;
65                Modify::Present(a, v)
66            }
67            ProtoModify::Removed(a, v) => {
68                let a = Attribute::from(a.as_str());
69                let v = qs.clone_partialvalue(&a, v)?;
70                Modify::Removed(a, v)
71            }
72            ProtoModify::Purged(a) => Modify::Purged(Attribute::from(a.as_str())),
73        })
74    }
75}
76
77#[derive(Clone, Debug, Default)]
78pub struct ModifyList<VALID> {
79    // This is never read, it's just used for state machine enforcement.
80    #[allow(dead_code)]
81    valid: VALID,
82    // The order of this list matters. Each change must be done in order.
83    mods: Vec<Modify>,
84}
85
86impl<'a> IntoIterator for &'a ModifyList<ModifyValid> {
87    type IntoIter = slice::Iter<'a, Modify>;
88    type Item = &'a Modify;
89
90    fn into_iter(self) -> Self::IntoIter {
91        self.mods.iter()
92    }
93}
94
95impl ModifyList<ModifyInvalid> {
96    pub fn new() -> Self {
97        ModifyList {
98            valid: ModifyInvalid,
99            mods: Vec::with_capacity(0),
100        }
101    }
102
103    pub fn new_list(mods: Vec<Modify>) -> Self {
104        ModifyList {
105            valid: ModifyInvalid,
106            mods,
107        }
108    }
109
110    pub fn new_purge_and_set(attr: Attribute, v: Value) -> Self {
111        Self::new_list(vec![m_purge(attr.clone()), Modify::Present(attr, v)])
112    }
113
114    pub fn new_append(attr: Attribute, v: Value) -> Self {
115        Self::new_list(vec![Modify::Present(attr, v)])
116    }
117
118    pub fn new_remove(attr: Attribute, pv: PartialValue) -> Self {
119        Self::new_list(vec![Modify::Removed(attr, pv)])
120    }
121
122    pub fn new_purge(attr: Attribute) -> Self {
123        Self::new_list(vec![m_purge(attr)])
124    }
125
126    pub fn new_set(attr: Attribute, vs: ValueSet) -> Self {
127        Self::new_list(vec![Modify::Set(attr, vs)])
128    }
129
130    pub fn push_mod(&mut self, modify: Modify) {
131        self.mods.push(modify)
132    }
133
134    pub fn from(
135        ml: &ProtoModifyList,
136        qs: &mut QueryServerWriteTransaction,
137    ) -> Result<Self, OperationError> {
138        // For each ProtoModify, do a from.
139        let inner: Result<Vec<_>, _> = ml.mods.iter().map(|pm| Modify::from(pm, qs)).collect();
140        match inner {
141            Ok(m) => Ok(ModifyList {
142                valid: ModifyInvalid,
143                mods: m,
144            }),
145            Err(e) => Err(e),
146        }
147    }
148
149    pub fn from_patch(
150        pe: &ProtoEntry,
151        qs: &mut QueryServerWriteTransaction,
152    ) -> Result<Self, OperationError> {
153        let mut mods = Vec::with_capacity(0);
154
155        pe.attrs.iter().try_for_each(|(attr, vals)| {
156            // Issue a purge to the attr.
157            let attr: Attribute = attr.as_str().into();
158            mods.push(m_purge(attr.clone()));
159            // Now if there are vals, push those too.
160            // For each value we want to now be present.
161            vals.iter().try_for_each(|val| {
162                qs.clone_value(&attr, val).map(|resolved_v| {
163                    mods.push(Modify::Present(attr.clone(), resolved_v));
164                })
165            })
166        })?;
167        Ok(ModifyList {
168            valid: ModifyInvalid,
169            mods,
170        })
171    }
172
173    pub fn validate(
174        &self,
175        schema: &dyn SchemaTransaction,
176    ) -> Result<ModifyList<ModifyValid>, SchemaError> {
177        let schema_attributes = schema.get_attributes();
178        /*
179        let schema_name = schema_attributes
180            .get(Attribute::Name.as_ref()")
181            .expect("Critical: Core schema corrupt or missing. To initiate a core transfer, please deposit substitute core in receptacle.");
182        */
183
184        let res: Result<Vec<Modify>, _> = self
185            .mods
186            .iter()
187            .map(|m| match m {
188                Modify::Present(attr, value) => match schema_attributes.get(attr) {
189                    Some(schema_a) => schema_a
190                        .validate_value(attr, value)
191                        .map(|_| Modify::Present(attr.clone(), value.clone())),
192                    None => Err(SchemaError::InvalidAttribute(attr.to_string())),
193                },
194                Modify::Removed(attr, value) => match schema_attributes.get(attr) {
195                    Some(schema_a) => schema_a
196                        .validate_partialvalue(attr, value)
197                        .map(|_| Modify::Removed(attr.clone(), value.clone())),
198                    None => Err(SchemaError::InvalidAttribute(attr.to_string())),
199                },
200                Modify::Assert(attr, value) => match schema_attributes.get(attr) {
201                    Some(schema_a) => schema_a
202                        .validate_partialvalue(attr, value)
203                        .map(|_| Modify::Assert(attr.clone(), value.clone())),
204                    None => Err(SchemaError::InvalidAttribute(attr.to_string())),
205                },
206                Modify::Purged(attr) => match schema_attributes.get(attr) {
207                    Some(_attr_name) => Ok(Modify::Purged(attr.clone())),
208                    None => Err(SchemaError::InvalidAttribute(attr.to_string())),
209                },
210                Modify::Set(attr, valueset) => match schema_attributes.get(attr) {
211                    Some(_attr_name) => Ok(Modify::Set(attr.clone(), valueset.clone())),
212                    None => Err(SchemaError::InvalidAttribute(attr.to_string())),
213                },
214            })
215            .collect();
216
217        let valid_mods = res?;
218
219        // Return new ModifyList!
220        Ok(ModifyList {
221            valid: ModifyValid,
222            mods: valid_mods,
223        })
224    }
225
226    /// ⚠️  - Convert a modlist to be considered valid, bypassing schema.
227    /// This is a TEST ONLY method and will never be exposed in production.
228    #[cfg(test)]
229    pub(crate) fn into_valid(self) -> ModifyList<ModifyValid> {
230        ModifyList {
231            valid: ModifyValid,
232            mods: self.mods,
233        }
234    }
235}
236
237impl From<BTreeMap<Attribute, Option<ValueSet>>> for ModifyList<ModifyInvalid> {
238    fn from(attrs: BTreeMap<Attribute, Option<ValueSet>>) -> Self {
239        let mods = attrs
240            .into_iter()
241            .map(|(attr, maybe_valueset)| {
242                if let Some(valueset) = maybe_valueset {
243                    Modify::Set(attr, valueset)
244                } else {
245                    Modify::Purged(attr)
246                }
247            })
248            .collect();
249
250        ModifyList {
251            valid: ModifyInvalid,
252            mods,
253        }
254    }
255}
256
257impl ModifyList<ModifyValid> {
258    /// ⚠️  - Create a new modlist that is considered valid, bypassing schema.
259    /// This is a TEST ONLY method and will never be exposed in production.
260    #[cfg(test)]
261    pub fn new_valid_list(mods: Vec<Modify>) -> Self {
262        ModifyList {
263            valid: ModifyValid,
264            mods,
265        }
266    }
267
268    pub fn iter(&self) -> slice::Iter<Modify> {
269        self.mods.iter()
270    }
271}
272
273impl<VALID> ModifyList<VALID> {
274    pub fn len(&self) -> usize {
275        self.mods.len()
276    }
277
278    pub fn is_empty(&self) -> bool {
279        self.len() == 0
280    }
281}