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 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 mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
53 .iter()
54 .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 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 .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 admin_error!("Delete operation failed (backend), {:?}", e);
93 e
94 })?;
95
96 Plugins::run_post_delete(self, &del_cand, de).map_err(|e| {
98 admin_error!("Delete operation failed (plugin), {:?}", e);
99 e
100 })?;
101
102 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 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 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 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 let de_inv = DeleteEvent::new_internal_invalid(filter!(f_pres(Attribute::NonExist)));
276 assert!(server_txn.delete(&de_inv).is_err());
277
278 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 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 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}