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 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        let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
53            .iter()
54            // Invalidate and assign change id's
55            .map(|er| {
56                er.as_ref()
57                    .clone()
58                    .invalidate(self.cid.clone(), &self.trim_cid)
59            })
60            .collect();
61
62        trace!(?candidates, "delete: candidates");
63
64        // Pre delete plugs
65        Plugins::run_pre_delete(self, &mut candidates, de).map_err(|e| {
66            admin_error!("Delete operation failed (plugin), {:?}", e);
67            e
68        })?;
69
70        trace!(?candidates, "delete: now marking candidates as recycled");
71
72        let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
73            .into_iter()
74            .map(|e| {
75                e.to_recycled()
76                    .validate(&self.schema)
77                    .map_err(|e| {
78                        admin_error!(err = ?e, "Schema Violation in delete validate");
79                        OperationError::SchemaViolation(e)
80                    })
81                    // seal if it worked.
82                    .map(|e| e.seal(&self.schema))
83            })
84            .collect();
85
86        let del_cand: Vec<Entry<_, _>> = res?;
87
88        self.be_txn
89            .modify(&self.cid, &pre_candidates, &del_cand)
90            .map_err(|e| {
91                // be_txn is dropped, ie aborted here.
92                admin_error!("Delete operation failed (backend), {:?}", e);
93                e
94            })?;
95
96        // Post delete plugins
97        Plugins::run_post_delete(self, &del_cand, de).map_err(|e| {
98            admin_error!("Delete operation failed (plugin), {:?}", e);
99            e
100        })?;
101
102        // We have finished all plugs and now have a successful operation - flag if
103        // schema or acp requires reload.
104        if !self.changed_flags.contains(ChangeFlag::SCHEMA)
105            && del_cand.iter().any(|e| {
106                e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
107                    || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
108            })
109        {
110            self.changed_flags.insert(ChangeFlag::SCHEMA)
111        }
112        if !self.changed_flags.contains(ChangeFlag::ACP)
113            && del_cand.iter().any(|e| {
114                e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
115            })
116        {
117            self.changed_flags.insert(ChangeFlag::ACP)
118        }
119
120        if !self.changed_flags.contains(ChangeFlag::APPLICATION)
121            && del_cand
122                .iter()
123                .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
124        {
125            self.changed_flags.insert(ChangeFlag::APPLICATION)
126        }
127
128        if !self.changed_flags.contains(ChangeFlag::OAUTH2)
129            && del_cand.iter().any(|e| {
130                e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
131            })
132        {
133            self.changed_flags.insert(ChangeFlag::OAUTH2)
134        }
135        if !self.changed_flags.contains(ChangeFlag::DOMAIN)
136            && del_cand
137                .iter()
138                .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
139        {
140            self.changed_flags.insert(ChangeFlag::DOMAIN)
141        }
142        if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
143            && del_cand
144                .iter()
145                .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
146        {
147            self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
148        }
149        if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
150            && del_cand
151                .iter()
152                .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
153        {
154            self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
155        }
156        if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
157            && del_cand.iter().any(|e| {
158                e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
159                    || e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
160            })
161        {
162            self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
163        }
164
165        self.changed_uuid
166            .extend(del_cand.iter().map(|e| e.get_uuid()));
167
168        trace!(
169            changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
170        );
171
172        // Send result
173        if de.ident.is_internal() {
174            trace!("Delete operation success");
175        } else {
176            admin_info!("Delete operation success");
177        }
178        Ok(())
179    }
180
181    pub fn internal_delete(
182        &mut self,
183        filter: &Filter<FilterInvalid>,
184    ) -> Result<(), OperationError> {
185        let f_valid = filter
186            .validate(self.get_schema())
187            .map_err(OperationError::SchemaViolation)?;
188        let de = DeleteEvent::new_internal(f_valid);
189        self.delete(&de)
190    }
191
192    pub fn internal_delete_uuid(&mut self, target_uuid: Uuid) -> Result<(), OperationError> {
193        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
194        let f_valid = filter
195            .validate(self.get_schema())
196            .map_err(OperationError::SchemaViolation)?;
197        let de = DeleteEvent::new_internal(f_valid);
198        self.delete(&de)
199    }
200
201    #[instrument(level = "debug", skip(self))]
202    pub fn internal_delete_uuid_if_exists(
203        &mut self,
204        target_uuid: Uuid,
205    ) -> Result<(), OperationError> {
206        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
207        let f_valid = filter
208            .validate(self.get_schema())
209            .map_err(OperationError::SchemaViolation)?;
210
211        let ee = ExistsEvent::new_internal(f_valid.clone());
212        // Submit it
213        if self.exists(&ee)? {
214            let de = DeleteEvent::new_internal(f_valid);
215            self.delete(&de)?;
216        }
217        Ok(())
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use crate::prelude::*;
224
225    #[qs_test]
226    async fn test_delete(server: &QueryServer) {
227        // Create
228        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
229
230        let e1 = entry_init!(
231            (Attribute::Class, EntryClass::Object.to_value()),
232            (Attribute::Class, EntryClass::Account.to_value()),
233            (Attribute::Class, EntryClass::Person.to_value()),
234            (Attribute::Name, Value::new_iname("testperson1")),
235            (
236                Attribute::Uuid,
237                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
238            ),
239            (Attribute::Description, Value::new_utf8s("testperson")),
240            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
241        );
242
243        let e2 = entry_init!(
244            (Attribute::Class, EntryClass::Object.to_value()),
245            (Attribute::Class, EntryClass::Account.to_value()),
246            (Attribute::Class, EntryClass::Person.to_value()),
247            (Attribute::Name, Value::new_iname("testperson2")),
248            (
249                Attribute::Uuid,
250                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
251            ),
252            (Attribute::Description, Value::new_utf8s("testperson")),
253            (Attribute::DisplayName, Value::new_utf8s("testperson2"))
254        );
255
256        let e3 = entry_init!(
257            (Attribute::Class, EntryClass::Object.to_value()),
258            (Attribute::Class, EntryClass::Account.to_value()),
259            (Attribute::Class, EntryClass::Person.to_value()),
260            (Attribute::Name, Value::new_iname("testperson3")),
261            (
262                Attribute::Uuid,
263                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63933"))
264            ),
265            (Attribute::Description, Value::new_utf8s("testperson")),
266            (Attribute::DisplayName, Value::new_utf8s("testperson3"))
267        );
268
269        let ce = CreateEvent::new_internal(vec![e1, e2, e3]);
270
271        let cr = server_txn.create(&ce);
272        assert!(cr.is_ok());
273
274        // Delete filter is syntax invalid
275        let de_inv = DeleteEvent::new_internal_invalid(filter!(f_pres(Attribute::NonExist)));
276        assert!(server_txn.delete(&de_inv).is_err());
277
278        // Delete deletes nothing
279        let de_empty = DeleteEvent::new_internal_invalid(filter!(f_eq(
280            Attribute::Uuid,
281            PartialValue::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-000000000000"))
282        )));
283        assert!(server_txn.delete(&de_empty).is_err());
284
285        // Delete matches one
286        let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
287            Attribute::Name,
288            PartialValue::new_iname("testperson3")
289        )));
290        assert!(server_txn.delete(&de_sin).is_ok());
291
292        // Delete matches many
293        let de_mult = DeleteEvent::new_internal_invalid(filter!(f_eq(
294            Attribute::Description,
295            PartialValue::new_utf8s("testperson")
296        )));
297        assert!(server_txn.delete(&de_mult).is_ok());
298
299        assert!(server_txn.commit().is_ok());
300    }
301}