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 if !de.ident.is_internal() {
17 security_info!(name = %de.ident, "delete initiator");
18 }
19
20 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 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 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 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 .map(|er| {
86 er.as_ref()
87 .clone()
88 .invalidate(self.cid.clone(), &self.trim_cid)
89 })
90 .map(|mut entry| {
93 if let Some(refer_uuid) = entry.get_ava_single_refer(Attribute::Refers) {
94 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 .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 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 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 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 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 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 .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 admin_error!("Delete operation failed (backend), {:?}", e);
201 e
202 })?;
203
204 Plugins::run_post_delete(self, &del_cand, de).map_err(|e| {
206 admin_error!("Delete operation failed (plugin), {:?}", e);
207 e
208 })?;
209
210 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 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 #[instrument(level = "debug", skip(self))]
330 pub fn internal_delete_uuid_if_exists(
331 &mut self,
332 target_uuid: Uuid,
333 ) -> Result<(), OperationError> {
334 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
335 let f_valid = filter
336 .validate(self.get_schema())
337 .map_err(OperationError::SchemaViolation)?;
338
339 let ee = ExistsEvent::new_internal(f_valid.clone());
340 if self.exists(&ee)? {
342 let de = DeleteEvent::new_internal(f_valid);
343 self.delete(&de)?;
344 }
345 Ok(())
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use crate::prelude::*;
352
353 #[qs_test]
354 async fn test_delete(server: &QueryServer) {
355 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
357
358 let e1 = entry_init!(
359 (Attribute::Class, EntryClass::Object.to_value()),
360 (Attribute::Class, EntryClass::Account.to_value()),
361 (Attribute::Class, EntryClass::Person.to_value()),
362 (Attribute::Name, Value::new_iname("testperson1")),
363 (
364 Attribute::Uuid,
365 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
366 ),
367 (Attribute::Description, Value::new_utf8s("testperson")),
368 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
369 );
370
371 let e2 = entry_init!(
372 (Attribute::Class, EntryClass::Object.to_value()),
373 (Attribute::Class, EntryClass::Account.to_value()),
374 (Attribute::Class, EntryClass::Person.to_value()),
375 (Attribute::Name, Value::new_iname("testperson2")),
376 (
377 Attribute::Uuid,
378 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
379 ),
380 (Attribute::Description, Value::new_utf8s("testperson")),
381 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
382 );
383
384 let e3 = entry_init!(
385 (Attribute::Class, EntryClass::Object.to_value()),
386 (Attribute::Class, EntryClass::Account.to_value()),
387 (Attribute::Class, EntryClass::Person.to_value()),
388 (Attribute::Name, Value::new_iname("testperson3")),
389 (
390 Attribute::Uuid,
391 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63933"))
392 ),
393 (Attribute::Description, Value::new_utf8s("testperson")),
394 (Attribute::DisplayName, Value::new_utf8s("testperson3"))
395 );
396
397 let ce = CreateEvent::new_internal(vec![e1, e2, e3]);
398
399 let cr = server_txn.create(&ce);
400 assert!(cr.is_ok());
401
402 let de_inv = DeleteEvent::new_internal_invalid(filter!(f_pres(Attribute::NonExist)));
404 assert!(server_txn.delete(&de_inv).is_err());
405
406 let de_empty = DeleteEvent::new_internal_invalid(filter!(f_eq(
408 Attribute::Uuid,
409 PartialValue::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-000000000000"))
410 )));
411 assert!(server_txn.delete(&de_empty).is_err());
412
413 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
415 Attribute::Name,
416 PartialValue::new_iname("testperson3")
417 )));
418 assert!(server_txn.delete(&de_sin).is_ok());
419
420 let de_mult = DeleteEvent::new_internal_invalid(filter!(f_eq(
422 Attribute::Description,
423 PartialValue::new_utf8s("testperson")
424 )));
425 assert!(server_txn.delete(&de_mult).is_ok());
426
427 assert!(server_txn.commit().is_ok());
428 }
429}