Skip to main content

kanidmd_lib/server/
delete.rs

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