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 if !de.ident.is_internal() {
16 security_info!(name = %de.ident, "delete initiator");
17 }
18
19 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 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 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 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 .map(|er| {
85 er.as_ref()
86 .clone()
87 .invalidate(self.cid.clone(), &self.trim_cid)
88 })
89 .map(|mut entry| {
92 if let Some(refer_uuid) = entry.get_ava_single_refer(Attribute::Refers) {
93 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 .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 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 .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 admin_error!("Delete operation failed (backend), {:?}", e);
146 e
147 })?;
148
149 Plugins::run_post_delete(self, &del_cand, de).map_err(|e| {
151 admin_error!("Delete operation failed (plugin), {:?}", e);
152 e
153 })?;
154
155 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 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 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 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 let de_inv = DeleteEvent::new_internal_invalid(filter!(f_pres(Attribute::NonExist)));
329 assert!(server_txn.delete(&de_inv).is_err());
330
331 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 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 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}