kanidmd_lib/server/
delete.rs

1use crate::prelude::*;
2use crate::server::DeleteEvent;
3use crate::server::{ChangeFlag, Plugins};
4
5impl QueryServerWriteTransaction<'_> {
6    #[allow(clippy::cognitive_complexity)]
7    #[instrument(level = "debug", skip_all)]
8    pub fn delete(&mut self, de: &DeleteEvent) -> Result<(), OperationError> {
9        // Do you have access to view all the set members? Reduce based on your
10        // read permissions and attrs
11        // THIS IS PRETTY COMPLEX SEE THE DESIGN DOC
12        // In this case we need a search, but not INTERNAL to keep the same
13        // associated credentials.
14        // We only need to retrieve uuid though ...
15        if !de.ident.is_internal() {
16            security_info!(name = %de.ident, "delete initiator");
17        }
18
19        // Now, delete only what you can see
20        let mut pre_candidates = self
21            .impersonate_search_valid(de.filter.clone(), de.filter_orig.clone(), &de.ident)
22            .map_err(|e| {
23                admin_error!("delete: error in pre-candidate selection {:?}", e);
24                e
25            })?;
26
27        // Apply access controls to reduce the set if required.
28        // delete_allow_operation
29        let access = self.get_accesscontrols();
30        let op_allow = access
31            .delete_allow_operation(de, &pre_candidates)
32            .map_err(|e| {
33                admin_error!("Failed to check delete access {:?}", e);
34                e
35            })?;
36        if !op_allow {
37            return Err(OperationError::AccessDenied);
38        }
39
40        // Is the candidate set empty?
41        if pre_candidates.is_empty() {
42            warn!("delete: no candidates match filter");
43            debug!(delete_filter = ?de.filter);
44            return Err(OperationError::NoMatchingEntries);
45        };
46
47        if pre_candidates.iter().any(|e| e.mask_tombstone().is_none()) {
48            warn!("Refusing to delete entries which may be an attempt to bypass replication state machine.");
49            return Err(OperationError::AccessDenied);
50        }
51
52        // ======= Access Control and Invariants Checked !!! ========
53
54        // We now extend pre-candidates with anything that will be cascade-deleted.
55        let references_filt = filter!(f_or(
56            pre_candidates
57                .iter()
58                .map(|entry| { f_eq(Attribute::Refers, PartialValue::Refer(entry.get_uuid())) })
59                .collect(),
60        ));
61
62        let mut pre_cascade_delete_candidates = self
63            .internal_search(references_filt)
64            .inspect_err(|err| error!(?err, "unable to find reference entries"))?;
65
66        #[cfg(any(test, debug_assertions))]
67        {
68            use std::collections::BTreeSet;
69
70            let candidate_uuids: BTreeSet<_> =
71                pre_candidates.iter().map(|e| e.get_uuid()).collect();
72            let ref_candidate_uuids: BTreeSet<_> = pre_cascade_delete_candidates
73                .iter()
74                .map(|e| e.get_uuid())
75                .collect();
76
77            assert!(candidate_uuids.is_disjoint(&ref_candidate_uuids));
78        }
79
80        let mut cascade_delete_candidates: Vec<Entry<EntryInvalid, EntryCommitted>> =
81            pre_cascade_delete_candidates
82                .iter()
83                // Invalidate and assign change id's
84                .map(|er| {
85                    er.as_ref()
86                        .clone()
87                        .invalidate(self.cid.clone(), &self.trim_cid)
88                })
89                // These entries are the ones that are being deleted by cascade, so we mark them
90                // as such.
91                .map(|mut entry| {
92                    if let Some(refer_uuid) = entry.get_ava_single_refer(Attribute::Refers) {
93                        // Stash the entry that triggered our deleted in this attribute. This
94                        // allows us to restore this linkage on revive, and also being a uuid instead
95                        // of a refers means that refint won't clean this linkage.
96                        entry.add_ava(Attribute::CascadeDeleted, Value::Uuid(refer_uuid));
97                    };
98                    entry
99                })
100                .collect();
101
102        let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
103            .iter()
104            // Invalidate and assign change id's
105            .map(|er| {
106                er.as_ref()
107                    .clone()
108                    .invalidate(self.cid.clone(), &self.trim_cid)
109            })
110            .collect();
111
112        pre_candidates.append(&mut pre_cascade_delete_candidates);
113        candidates.append(&mut cascade_delete_candidates);
114
115        trace!(?candidates, "delete: candidates");
116
117        // Pre delete plugs
118        Plugins::run_pre_delete(self, &mut candidates, de).map_err(|e| {
119            admin_error!("Delete operation failed (plugin), {:?}", e);
120            e
121        })?;
122
123        trace!(?candidates, "delete: now marking candidates as recycled");
124
125        let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
126            .into_iter()
127            .map(|e| {
128                e.to_recycled()
129                    .validate(&self.schema)
130                    .map_err(|e| {
131                        admin_error!(err = ?e, "Schema Violation in delete validate");
132                        OperationError::SchemaViolation(e)
133                    })
134                    // seal if it worked.
135                    .map(|e| e.seal(&self.schema))
136            })
137            .collect();
138
139        let del_cand: Vec<Entry<_, _>> = res?;
140
141        self.be_txn
142            .modify(&self.cid, &pre_candidates, &del_cand)
143            .map_err(|e| {
144                // be_txn is dropped, ie aborted here.
145                admin_error!("Delete operation failed (backend), {:?}", e);
146                e
147            })?;
148
149        // Post delete plugins
150        Plugins::run_post_delete(self, &del_cand, de).map_err(|e| {
151            admin_error!("Delete operation failed (plugin), {:?}", e);
152            e
153        })?;
154
155        // We have finished all plugs and now have a successful operation - flag if
156        // schema or acp requires reload.
157        if !self.changed_flags.contains(ChangeFlag::SCHEMA)
158            && del_cand.iter().any(|e| {
159                e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
160                    || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
161            })
162        {
163            self.changed_flags.insert(ChangeFlag::SCHEMA)
164        }
165        if !self.changed_flags.contains(ChangeFlag::ACP)
166            && del_cand.iter().any(|e| {
167                e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
168            })
169        {
170            self.changed_flags.insert(ChangeFlag::ACP)
171        }
172
173        if !self.changed_flags.contains(ChangeFlag::APPLICATION)
174            && del_cand
175                .iter()
176                .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
177        {
178            self.changed_flags.insert(ChangeFlag::APPLICATION)
179        }
180
181        if !self.changed_flags.contains(ChangeFlag::OAUTH2)
182            && del_cand.iter().any(|e| {
183                e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
184            })
185        {
186            self.changed_flags.insert(ChangeFlag::OAUTH2)
187        }
188        if !self.changed_flags.contains(ChangeFlag::DOMAIN)
189            && del_cand
190                .iter()
191                .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
192        {
193            self.changed_flags.insert(ChangeFlag::DOMAIN)
194        }
195        if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
196            && del_cand
197                .iter()
198                .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
199        {
200            self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
201        }
202        if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
203            && del_cand
204                .iter()
205                .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
206        {
207            self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
208        }
209        if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
210            && del_cand.iter().any(|e| {
211                e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
212                    || e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
213            })
214        {
215            self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
216        }
217
218        self.changed_uuid
219            .extend(del_cand.iter().map(|e| e.get_uuid()));
220
221        trace!(
222            changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
223        );
224
225        // Send result
226        if de.ident.is_internal() {
227            trace!("Delete operation success");
228        } else {
229            admin_info!("Delete operation success");
230        }
231        Ok(())
232    }
233
234    pub fn internal_delete(
235        &mut self,
236        filter: &Filter<FilterInvalid>,
237    ) -> Result<(), OperationError> {
238        let f_valid = filter
239            .validate(self.get_schema())
240            .map_err(OperationError::SchemaViolation)?;
241        let de = DeleteEvent::new_internal(f_valid);
242        self.delete(&de)
243    }
244
245    pub fn internal_delete_uuid(&mut self, target_uuid: Uuid) -> Result<(), OperationError> {
246        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
247        let f_valid = filter
248            .validate(self.get_schema())
249            .map_err(OperationError::SchemaViolation)?;
250        let de = DeleteEvent::new_internal(f_valid);
251        self.delete(&de)
252    }
253
254    #[instrument(level = "debug", skip(self))]
255    pub fn internal_delete_uuid_if_exists(
256        &mut self,
257        target_uuid: Uuid,
258    ) -> Result<(), OperationError> {
259        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
260        let f_valid = filter
261            .validate(self.get_schema())
262            .map_err(OperationError::SchemaViolation)?;
263
264        let ee = ExistsEvent::new_internal(f_valid.clone());
265        // Submit it
266        if self.exists(&ee)? {
267            let de = DeleteEvent::new_internal(f_valid);
268            self.delete(&de)?;
269        }
270        Ok(())
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use crate::prelude::*;
277
278    #[qs_test]
279    async fn test_delete(server: &QueryServer) {
280        // Create
281        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
282
283        let e1 = entry_init!(
284            (Attribute::Class, EntryClass::Object.to_value()),
285            (Attribute::Class, EntryClass::Account.to_value()),
286            (Attribute::Class, EntryClass::Person.to_value()),
287            (Attribute::Name, Value::new_iname("testperson1")),
288            (
289                Attribute::Uuid,
290                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
291            ),
292            (Attribute::Description, Value::new_utf8s("testperson")),
293            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
294        );
295
296        let e2 = entry_init!(
297            (Attribute::Class, EntryClass::Object.to_value()),
298            (Attribute::Class, EntryClass::Account.to_value()),
299            (Attribute::Class, EntryClass::Person.to_value()),
300            (Attribute::Name, Value::new_iname("testperson2")),
301            (
302                Attribute::Uuid,
303                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
304            ),
305            (Attribute::Description, Value::new_utf8s("testperson")),
306            (Attribute::DisplayName, Value::new_utf8s("testperson2"))
307        );
308
309        let e3 = entry_init!(
310            (Attribute::Class, EntryClass::Object.to_value()),
311            (Attribute::Class, EntryClass::Account.to_value()),
312            (Attribute::Class, EntryClass::Person.to_value()),
313            (Attribute::Name, Value::new_iname("testperson3")),
314            (
315                Attribute::Uuid,
316                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63933"))
317            ),
318            (Attribute::Description, Value::new_utf8s("testperson")),
319            (Attribute::DisplayName, Value::new_utf8s("testperson3"))
320        );
321
322        let ce = CreateEvent::new_internal(vec![e1, e2, e3]);
323
324        let cr = server_txn.create(&ce);
325        assert!(cr.is_ok());
326
327        // Delete filter is syntax invalid
328        let de_inv = DeleteEvent::new_internal_invalid(filter!(f_pres(Attribute::NonExist)));
329        assert!(server_txn.delete(&de_inv).is_err());
330
331        // Delete deletes nothing
332        let de_empty = DeleteEvent::new_internal_invalid(filter!(f_eq(
333            Attribute::Uuid,
334            PartialValue::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-000000000000"))
335        )));
336        assert!(server_txn.delete(&de_empty).is_err());
337
338        // Delete matches one
339        let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
340            Attribute::Name,
341            PartialValue::new_iname("testperson3")
342        )));
343        assert!(server_txn.delete(&de_sin).is_ok());
344
345        // Delete matches many
346        let de_mult = DeleteEvent::new_internal_invalid(filter!(f_eq(
347            Attribute::Description,
348            PartialValue::new_utf8s("testperson")
349        )));
350        assert!(server_txn.delete(&de_mult).is_ok());
351
352        assert!(server_txn.commit().is_ok());
353    }
354}