1use super::modify::ModifyPartial;
2use crate::event::ReviveRecycledEvent;
3use crate::prelude::*;
4use crate::server::Plugins;
5use std::collections::BTreeMap;
6
7impl QueryServerWriteTransaction<'_> {
8 #[instrument(level = "debug", skip_all)]
9 pub fn purge_tombstones(&mut self) -> Result<usize, OperationError> {
10 let trim_cid = self.trim_cid().clone();
12 let anchor_cid = self.get_txn_cid().clone();
13
14 self.be_txn
16 .reap_tombstones(&anchor_cid, &trim_cid)
17 .map_err(|e| {
18 error!(err = ?e, "Tombstone purge operation failed (backend)");
19 e
20 })
21 .inspect(|_res| {
22 admin_info!("Tombstone purge operation success");
23 })
24 }
25
26 #[instrument(level = "debug", skip_all)]
27 pub fn purge_recycled(&mut self) -> Result<usize, OperationError> {
28 let cid = self.cid.sub_secs(RECYCLEBIN_MAX_AGE).map_err(|e| {
31 admin_error!(err = ?e, "Unable to generate search cid for purge_recycled");
32 e
33 })?;
34 let rc = self.internal_search(filter_all!(f_and!([
35 f_eq(Attribute::Class, EntryClass::Recycled.into()),
36 f_lt(Attribute::LastModifiedCid, PartialValue::new_cid(cid)),
37 ])))?;
38
39 if rc.is_empty() {
40 admin_debug!("No recycled items present - purge operation success");
41 return Ok(0);
42 }
43
44 let tombstone_cand: Result<Vec<_>, _> = rc
46 .iter()
47 .map(|e| {
48 e.to_tombstone(self.cid.clone())
49 .validate(&self.schema)
50 .map_err(|e| {
51 admin_error!("Schema Violation in purge_recycled validate: {:?}", e);
52 OperationError::SchemaViolation(e)
53 })
54 .map(|e| e.seal(&self.schema))
56 })
57 .collect();
58
59 let tombstone_cand = tombstone_cand?;
60 let touched = tombstone_cand.len();
63
64 self.be_txn
66 .modify(&self.cid, &rc, &tombstone_cand)
67 .map_err(|e| {
68 admin_error!("Purge recycled operation failed (backend), {:?}", e);
69 e
70 })
71 .map(|_| {
72 admin_info!("Purge recycled operation success");
73 touched
74 })
75 }
76
77 #[instrument(level = "debug", skip_all)]
78 pub fn purge_delete_after(&mut self) -> Result<usize, OperationError> {
80 let curtime_odt = self.get_curtime_odt();
81
82 let filter = filter!(f_and(vec![
83 f_pres(Attribute::DeleteAfter),
84 f_lt(Attribute::DeleteAfter, PartialValue::DateTime(curtime_odt))
85 ]));
86
87 let entries = self.internal_search(filter.clone())?;
89
90 if entries.is_empty() {
91 return Ok(0);
92 }
93
94 self.internal_delete(&filter)?;
95
96 Ok(entries.len())
97 }
98
99 #[instrument(level = "debug", skip_all)]
100 pub fn revive_recycled(&mut self, re: &ReviveRecycledEvent) -> Result<(), OperationError> {
101 if !re.ident.is_internal() {
107 security_info!(name = %re.ident, "revive initiator");
108 }
109
110 let mut pre_candidates =
112 self.impersonate_search_valid(re.filter.clone(), re.filter.clone(), &re.ident)?;
113
114 if pre_candidates.is_empty() {
116 if re.ident.is_internal() {
117 trace!(
118 "revive: no candidates match filter ... continuing {:?}",
119 re.filter
120 );
121 return Ok(());
122 } else {
123 error!(
124 "revive: no candidates match filter, failure {:?}",
125 re.filter
126 );
127 return Err(OperationError::NoMatchingEntries);
128 }
129 };
130
131 trace!("revive: pre_candidates -> {:?}", pre_candidates);
132
133 let modlist = ModifyList::new_list(vec![Modify::Removed(
135 Attribute::Class,
136 EntryClass::Recycled.into(),
137 )]);
138
139 let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
140 admin_error!("revive recycled modlist Schema Violation {:?}", e);
141 OperationError::SchemaViolation(e)
142 })?;
143
144 let me =
145 ModifyEvent::new_impersonate(&re.ident, re.filter.clone(), re.filter.clone(), m_valid);
146
147 let access = self.get_accesscontrols();
148 let op_allow = access
149 .modify_allow_operation(&me, &pre_candidates)
150 .map_err(|e| {
151 admin_error!("Unable to check modify access {:?}", e);
152 e
153 })?;
154 if !op_allow {
155 return Err(OperationError::AccessDenied);
156 }
157
158 if pre_candidates.iter().all(|e| e.mask_recycled().is_some()) {
160 admin_warn!("Refusing to revive entries that are already live!");
161 return Err(OperationError::AccessDenied);
162 }
163
164 let references_filt = filter_rec!(f_or(
169 pre_candidates
170 .iter()
171 .map(|entry| {
172 f_eq(
173 Attribute::CascadeDeleted,
174 PartialValue::Uuid(entry.get_uuid()),
175 )
176 })
177 .collect(),
178 ));
179
180 let mut pre_cascade_revive_candidates = self
181 .internal_search(references_filt)
182 .inspect_err(|err| error!(?err, "unable to find reference entries"))?;
183
184 pre_candidates.append(&mut pre_cascade_revive_candidates);
185
186 let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
188 .iter()
189 .map(|er| {
190 er.as_ref()
191 .clone()
192 .invalidate(self.cid.clone(), &self.trim_cid)
193 })
194 .map(|mut entry| {
196 if let Some(refers_uuid) = entry.get_ava_single_uuid(Attribute::CascadeDeleted) {
197 entry.set_ava_set(&Attribute::Refers, ValueSetRefer::new(refers_uuid));
198 }
199 entry
200 })
201 .map(|er| er.to_revived())
203 .collect();
204
205 if candidates.iter().all(|e| e.mask_recycled().is_none()) {
207 admin_error!("Not all candidates were correctly revived, unable to proceed");
208 return Err(OperationError::InvalidEntryState);
209 }
210
211 Plugins::run_pre_modify(self, &pre_candidates, &mut candidates, &me).map_err(|e| {
214 admin_error!("Revive operation failed (plugin), {:?}", e);
215 e
216 })?;
217
218 let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
220 .into_iter()
221 .map(|e| {
222 e.validate(&self.schema)
223 .map_err(|e| {
224 admin_error!("Schema Violation {:?}", e);
225 OperationError::SchemaViolation(e)
226 })
227 .map(|e| e.seal(&self.schema))
228 })
229 .collect();
230
231 let norm_cand: Vec<Entry<_, _>> = res?;
232
233 let mut dm_mods: BTreeMap<Uuid, ModifyList<ModifyInvalid>> = Default::default();
235
236 for entry in &pre_candidates {
237 let u: Uuid = entry.get_uuid();
239
240 if let Some(riter) = entry.get_ava_as_refuuid(Attribute::RecycledDirectMemberOf) {
241 for g_uuid in riter {
242 dm_mods
243 .entry(g_uuid)
244 .and_modify(|mlist| {
245 let m = Modify::Present(Attribute::Member, Value::Refer(u));
246 mlist.push_mod(m);
247 })
248 .or_insert({
249 let m = Modify::Present(Attribute::Member, Value::Refer(u));
250 ModifyList::new_list(vec![m])
251 });
252 }
253 }
254 }
255
256 let mp = ModifyPartial {
258 norm_cand,
259 pre_candidates,
260 me: &me,
261 };
262
263 self.modify_apply(mp)?;
265
266 for (g, mods) in dm_mods {
269 let f = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(g)));
273 self.internal_modify(&f, &mods)?;
274 }
275
276 Ok(())
277 }
278
279 #[cfg(test)]
280 pub(crate) fn internal_revive_uuid(&mut self, target_uuid: Uuid) -> Result<(), OperationError> {
281 let filter = filter_rec!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
283 let f_valid = filter
284 .validate(self.get_schema())
285 .map_err(OperationError::SchemaViolation)?;
286 let re = ReviveRecycledEvent::new_internal(f_valid);
287 self.revive_recycled(&re)
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::ReviveRecycledEvent;
294 use crate::event::{CreateEvent, DeleteEvent};
295 use crate::prelude::*;
296 use crate::server::ModifyEvent;
297 use crate::server::SearchEvent;
298 use crate::server::ValueSetMessage;
299 use kanidm_proto::v1::OutboundMessage;
300 use time::OffsetDateTime;
301
302 #[qs_test]
303 async fn test_recycle_simple(server: &QueryServer) {
304 let time_p1 = duration_from_epoch_now();
306 let time_p2 = time_p1 + Duration::from_secs(RECYCLEBIN_MAX_AGE * 2);
307
308 let mut server_txn = server.write(time_p1).await.unwrap();
309 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
310
311 let filt_i_rc = filter_all!(f_eq(Attribute::Class, EntryClass::Recycled.into()));
312
313 let filt_i_ts = filter_all!(f_eq(Attribute::Class, EntryClass::Tombstone.into()));
314
315 let filt_i_per = filter_all!(f_eq(Attribute::Class, EntryClass::Person.into()));
316
317 let me_rc = ModifyEvent::new_impersonate_entry(
319 admin.clone(),
320 filt_i_rc.clone(),
321 ModifyList::new_list(vec![Modify::Present(
322 Attribute::Class,
323 EntryClass::Recycled.into(),
324 )]),
325 );
326
327 let de_rc = DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_rc.clone());
328
329 let se_rc = SearchEvent::new_ext_impersonate_entry(admin.clone(), filt_i_rc.clone());
330
331 let sre_rc = SearchEvent::new_rec_impersonate_entry(admin.clone(), filt_i_rc.clone());
332
333 let rre_rc = ReviveRecycledEvent::new_impersonate_entry(
334 admin,
335 filter_all!(f_eq(
336 Attribute::Name,
337 PartialValue::new_iname("testperson1")
338 )),
339 );
340
341 let e1 = entry_init!(
343 (Attribute::Class, EntryClass::Object.to_value()),
344 (Attribute::Class, EntryClass::Account.to_value()),
345 (Attribute::Class, EntryClass::Person.to_value()),
346 (Attribute::Name, Value::new_iname("testperson1")),
347 (
348 Attribute::Uuid,
349 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
350 ),
351 (Attribute::Description, Value::new_utf8s("testperson1")),
352 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
353 );
354
355 let e2 = entry_init!(
356 (Attribute::Class, EntryClass::Object.to_value()),
357 (Attribute::Class, EntryClass::Account.to_value()),
358 (Attribute::Class, EntryClass::Person.to_value()),
359 (Attribute::Name, Value::new_iname("testperson2")),
360 (
361 Attribute::Uuid,
362 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
363 ),
364 (Attribute::Description, Value::new_utf8s("testperson2")),
365 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
366 );
367
368 let ce = CreateEvent::new_internal(vec![e1, e2]);
369 let cr = server_txn.create(&ce);
370 assert!(cr.is_ok());
371
372 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([
374 f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
375 f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
376 ])));
377 assert!(server_txn.delete(&de_sin).is_ok());
378
379 let r1 = server_txn.search(&se_rc).expect("search failed");
381 assert!(r1.is_empty());
382
383 assert!(server_txn.delete(&de_rc).is_err());
386
387 assert!(server_txn.modify(&me_rc).is_err());
390
391 let r2 = server_txn.search(&sre_rc).expect("search failed");
393 assert_eq!(r2.len(), 2);
394
395 let r2 = server_txn
398 .internal_search(filt_i_rc.clone())
399 .expect("internal search failed");
400 assert_eq!(r2.len(), 2);
401
402 assert!(server_txn.revive_recycled(&rre_rc).is_ok());
405
406 assert!(server_txn.purge_recycled().is_ok());
408 let r3 = server_txn
409 .internal_search(filt_i_rc.clone())
410 .expect("internal search failed");
411 assert_eq!(r3.len(), 1);
412
413 assert!(server_txn.commit().is_ok());
415
416 let mut server_txn = server.write(time_p2).await.unwrap();
418
419 assert!(server_txn.purge_recycled().is_ok());
421
422 let r4 = server_txn
424 .internal_search(filt_i_rc.clone())
425 .expect("internal search failed");
426 assert!(r4.is_empty());
427
428 let r5 = server_txn
430 .internal_search(filt_i_ts.clone())
431 .expect("internal search failed");
432 assert_eq!(r5.len(), 1);
433
434 let r6 = server_txn
436 .internal_search(filt_i_per.clone())
437 .expect("internal search failed");
438 assert_eq!(r6.len(), 1);
439
440 assert!(server_txn.commit().is_ok());
441 }
442
443 #[qs_test]
445 async fn test_qs_recycle_advanced(server: &QueryServer) {
446 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
448 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
449
450 let e1 = entry_init!(
451 (Attribute::Class, EntryClass::Object.to_value()),
452 (Attribute::Class, EntryClass::Account.to_value()),
453 (Attribute::Class, EntryClass::Person.to_value()),
454 (Attribute::Name, Value::new_iname("testperson1")),
455 (
456 Attribute::Uuid,
457 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
458 ),
459 (Attribute::Description, Value::new_utf8s("testperson1")),
460 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
461 );
462 let ce = CreateEvent::new_internal(vec![e1]);
463
464 let cr = server_txn.create(&ce);
465 assert!(cr.is_ok());
466 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
468 Attribute::Name,
469 PartialValue::new_iname("testperson1")
470 )));
471 assert!(server_txn.delete(&de_sin).is_ok());
472 let filt_rc = filter_all!(f_eq(Attribute::Class, EntryClass::Recycled.into()));
474 let sre_rc = SearchEvent::new_rec_impersonate_entry(admin, filt_rc);
475 let r2 = server_txn.search(&sre_rc).expect("search failed");
476 assert_eq!(r2.len(), 1);
477
478 let cr = server_txn.create(&ce);
481 assert!(cr.is_err());
482
483 assert!(server_txn.commit().is_ok());
484 }
485
486 #[qs_test]
487 async fn test_uuid_to_star_recycle(server: &QueryServer) {
488 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
489
490 let e1 = entry_init!(
491 (Attribute::Class, EntryClass::Object.to_value()),
492 (Attribute::Class, EntryClass::Person.to_value()),
493 (Attribute::Class, EntryClass::Account.to_value()),
494 (Attribute::Name, Value::new_iname("testperson1")),
495 (
496 Attribute::Uuid,
497 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
498 ),
499 (Attribute::Description, Value::new_utf8s("testperson1")),
500 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
501 );
502
503 let tuuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
504
505 let ce = CreateEvent::new_internal(vec![e1]);
506 let cr = server_txn.create(&ce);
507 assert!(cr.is_ok());
508
509 assert_eq!(
510 server_txn.uuid_to_rdn(tuuid),
511 Ok("spn=testperson1@example.com".to_string())
512 );
513
514 assert!(
515 server_txn.uuid_to_spn(tuuid)
516 == Ok(Some(Value::new_spn_str("testperson1", "example.com")))
517 );
518
519 assert_eq!(server_txn.name_to_uuid("testperson1"), Ok(tuuid));
520
521 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
523 Attribute::Name,
524 PartialValue::new_iname("testperson1")
525 )));
526 assert!(server_txn.delete(&de_sin).is_ok());
527
528 assert!(
530 server_txn.uuid_to_rdn(tuuid)
531 == Ok("uuid=cc8e95b4-c24f-4d68-ba54-8bed76f63930".to_string())
532 );
533
534 assert_eq!(server_txn.uuid_to_spn(tuuid), Ok(None));
535
536 assert!(server_txn.name_to_uuid("testperson1").is_err());
537
538 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
540 let rre_rc = ReviveRecycledEvent::new_impersonate_entry(
541 admin,
542 filter_all!(f_eq(
543 Attribute::Name,
544 PartialValue::new_iname("testperson1")
545 )),
546 );
547 assert!(server_txn.revive_recycled(&rre_rc).is_ok());
548
549 assert_eq!(
552 server_txn.uuid_to_rdn(tuuid),
553 Ok("spn=testperson1@example.com".to_string())
554 );
555
556 assert!(
557 server_txn.uuid_to_spn(tuuid)
558 == Ok(Some(Value::new_spn_str("testperson1", "example.com")))
559 );
560
561 assert_eq!(server_txn.name_to_uuid("testperson1"), Ok(tuuid));
562 }
563
564 #[qs_test]
565 async fn test_tombstone(server: &QueryServer) {
566 let time_p1 = duration_from_epoch_now();
568 let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
569 let time_p3 = time_p2 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
570
571 trace!("test_tombstone_start");
572 let mut server_txn = server.write(time_p1).await.unwrap();
573 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
574
575 let filt_i_ts = filter_all!(f_eq(Attribute::Class, EntryClass::Tombstone.into()));
576
577 let me_ts = ModifyEvent::new_impersonate_entry(
580 admin.clone(),
581 filt_i_ts.clone(),
582 ModifyList::new_list(vec![Modify::Present(
583 Attribute::Class,
584 EntryClass::Tombstone.into(),
585 )]),
586 );
587
588 let de_ts = DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_ts.clone());
589 let se_ts = SearchEvent::new_ext_impersonate_entry(admin, filt_i_ts.clone());
590
591 let e_ts = entry_init!(
593 (Attribute::Class, EntryClass::Object.to_value()),
594 (Attribute::Class, EntryClass::Account.to_value()),
595 (Attribute::Class, EntryClass::Person.to_value()),
596 (Attribute::Name, Value::new_iname("testperson1")),
597 (
598 Attribute::Uuid,
599 Value::Uuid(uuid!("9557f49c-97a5-4277-a9a5-097d17eb8317"))
600 ),
601 (Attribute::Description, Value::new_utf8s("testperson1")),
602 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
603 );
604
605 let ce = CreateEvent::new_internal(vec![e_ts]);
606 let cr = server_txn.create(&ce);
607 assert!(cr.is_ok());
608
609 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([f_eq(
610 Attribute::Name,
611 PartialValue::new_iname("testperson1")
612 )])));
613 assert!(server_txn.delete(&de_sin).is_ok());
614
615 assert!(server_txn.commit().is_ok());
617
618 let mut server_txn = server.write(time_p2).await.unwrap();
620 assert!(server_txn.purge_recycled().is_ok());
621
622 let r1 = server_txn.search(&se_ts).expect("search failed");
626 assert!(r1.is_empty());
627
628 assert!(server_txn.delete(&de_ts).is_err());
631
632 assert!(server_txn.modify(&me_ts).is_err());
635
636 let r2 = server_txn
639 .internal_search(filt_i_ts.clone())
640 .expect("internal search failed");
641 assert_eq!(r2.len(), 1);
642
643 assert!(server_txn.purge_tombstones().is_ok());
645
646 let r3 = server_txn
647 .internal_search(filt_i_ts.clone())
648 .expect("internal search failed");
649 assert_eq!(r3.len(), 1);
650
651 assert!(server_txn.commit().is_ok());
653
654 let mut server_txn = server.write(time_p3).await.unwrap();
656
657 assert!(server_txn.purge_tombstones().is_ok());
659
660 let r4 = server_txn
663 .internal_search(filt_i_ts)
664 .expect("internal search failed");
665 assert!(r4.is_empty());
666
667 assert!(server_txn.commit().is_ok());
668 }
669
670 fn create_user(name: &str, uuid: &str) -> Entry<EntryInit, EntryNew> {
671 entry_init!(
672 (Attribute::Class, EntryClass::Object.to_value()),
673 (Attribute::Class, EntryClass::Account.to_value()),
674 (Attribute::Class, EntryClass::Person.to_value()),
675 (Attribute::Name, Value::new_iname(name)),
676 (
677 Attribute::Uuid,
678 #[allow(clippy::panic)]
679 Value::new_uuid_s(uuid).unwrap_or_else(|| { panic!("{}", Attribute::Uuid) })
680 ),
681 (Attribute::Description, Value::new_utf8s("testperson-entry")),
682 (Attribute::DisplayName, Value::new_utf8s(name))
683 )
684 }
685
686 fn create_group(name: &str, uuid: &str, members: &[&str]) -> Entry<EntryInit, EntryNew> {
687 #[allow(clippy::panic)]
688 let mut e1 = entry_init!(
689 (Attribute::Class, EntryClass::Object.to_value()),
690 (Attribute::Class, EntryClass::Group.to_value()),
691 (Attribute::Name, Value::new_iname(name)),
692 (
693 Attribute::Uuid,
694 Value::new_uuid_s(uuid).unwrap_or_else(|| { panic!("{}", Attribute::Uuid) })
695 ),
696 (Attribute::Description, Value::new_utf8s("testgroup-entry"))
697 );
698 members
699 .iter()
700 .for_each(|m| e1.add_ava(Attribute::Member, Value::new_refer_s(m).unwrap()));
701 e1
702 }
703
704 fn check_entry_has_mo(qs: &mut QueryServerWriteTransaction, name: &str, mo: &str) -> bool {
705 let entry = qs
706 .internal_search(filter!(f_eq(
707 Attribute::Name,
708 PartialValue::new_iname(name)
709 )))
710 .unwrap()
711 .pop()
712 .unwrap();
713
714 trace!(?entry);
715
716 entry.attribute_equality(Attribute::MemberOf, &PartialValue::new_refer_s(mo).unwrap())
717 }
718
719 #[qs_test]
720 async fn test_revive_advanced_directmemberships(server: &QueryServer) {
721 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
723 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
724
725 let u1 = create_user("u1", "22b47373-d123-421f-859e-9ddd8ab14a2a");
727 let g1 = create_group(
728 "g1",
729 "cca2bbfc-5b43-43f3-be9e-f5b03b3defec",
730 &["22b47373-d123-421f-859e-9ddd8ab14a2a"],
731 );
732
733 let u2 = create_user("u2", "5c19a4a2-b9f0-4429-b130-5782de5fddda");
735 let g2a = create_group(
736 "g2a",
737 "e44cf9cd-9941-44cb-a02f-307b6e15ac54",
738 &["5c19a4a2-b9f0-4429-b130-5782de5fddda"],
739 );
740 let g2b = create_group(
741 "g2b",
742 "d3132e6e-18ce-4b87-bee1-1d25e4bfe96d",
743 &["e44cf9cd-9941-44cb-a02f-307b6e15ac54"],
744 );
745
746 let u3 = create_user("u3", "68467a41-6e8e-44d0-9214-a5164e75ca03");
748 let g3 = create_group(
749 "g3",
750 "36048117-e479-45ed-aeb5-611e8d83d5b1",
751 &["68467a41-6e8e-44d0-9214-a5164e75ca03"],
752 );
753
754 let u4 = create_user("u4", "d696b10f-1729-4f1a-83d0-ca06525c2f59");
757 let g4 = create_group(
758 "g4",
759 "d5c59ac6-c533-4b00-989f-d0e183f07bab",
760 &["d696b10f-1729-4f1a-83d0-ca06525c2f59"],
761 );
762
763 let ce = CreateEvent::new_internal(vec![u1, g1, u2, g2a, g2b, u3, g3, u4, g4]);
764 let cr = server_txn.create(&ce);
765 assert!(cr.is_ok());
766
767 let de = DeleteEvent::new_internal_invalid(filter!(f_or(vec![
769 f_eq(Attribute::Name, PartialValue::new_iname("u1")),
770 f_eq(Attribute::Name, PartialValue::new_iname("u2")),
771 f_eq(Attribute::Name, PartialValue::new_iname("u3")),
772 f_eq(Attribute::Name, PartialValue::new_iname("g3")),
773 f_eq(Attribute::Name, PartialValue::new_iname("u4")),
774 f_eq(Attribute::Name, PartialValue::new_iname("g4"))
775 ])));
776 assert!(server_txn.delete(&de).is_ok());
777
778 let rev1 = ReviveRecycledEvent::new_impersonate_entry(
780 admin.clone(),
781 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u1"))),
782 );
783 assert!(server_txn.revive_recycled(&rev1).is_ok());
784 assert!(check_entry_has_mo(
786 &mut server_txn,
787 "u1",
788 "cca2bbfc-5b43-43f3-be9e-f5b03b3defec"
789 ));
790
791 let rev2 = ReviveRecycledEvent::new_impersonate_entry(
793 admin.clone(),
794 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u2"))),
795 );
796 assert!(server_txn.revive_recycled(&rev2).is_ok());
797 assert!(check_entry_has_mo(
798 &mut server_txn,
799 "u2",
800 "e44cf9cd-9941-44cb-a02f-307b6e15ac54"
801 ));
802 assert!(check_entry_has_mo(
803 &mut server_txn,
804 "u2",
805 "d3132e6e-18ce-4b87-bee1-1d25e4bfe96d"
806 ));
807
808 let rev3 = ReviveRecycledEvent::new_impersonate_entry(
810 admin.clone(),
811 filter_all!(f_or(vec![
812 f_eq(Attribute::Name, PartialValue::new_iname("u3")),
813 f_eq(Attribute::Name, PartialValue::new_iname("g3"))
814 ])),
815 );
816 assert!(server_txn.revive_recycled(&rev3).is_ok());
817 assert!(!check_entry_has_mo(
818 &mut server_txn,
819 "u3",
820 "36048117-e479-45ed-aeb5-611e8d83d5b1"
821 ));
822
823 let rev4a = ReviveRecycledEvent::new_impersonate_entry(
825 admin.clone(),
826 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u4"))),
827 );
828 assert!(server_txn.revive_recycled(&rev4a).is_ok());
829 assert!(!check_entry_has_mo(
830 &mut server_txn,
831 "u4",
832 "d5c59ac6-c533-4b00-989f-d0e183f07bab"
833 ));
834
835 let rev4b = ReviveRecycledEvent::new_impersonate_entry(
837 admin,
838 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("g4"))),
839 );
840 assert!(server_txn.revive_recycled(&rev4b).is_ok());
841 assert!(!check_entry_has_mo(
842 &mut server_txn,
843 "u4",
844 "d5c59ac6-c533-4b00-989f-d0e183f07bab"
845 ));
846
847 assert!(server_txn.commit().is_ok());
848 }
849
850 #[qs_test]
851 async fn test_entry_delete_after(server: &QueryServer) {
852 let time_p1 = duration_from_epoch_now();
853 let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
854 let time_p3 = time_p2 + Duration::from_secs(1);
855
856 let odt_p1 = OffsetDateTime::UNIX_EPOCH + time_p1;
857 let odt_p2 = OffsetDateTime::UNIX_EPOCH + time_p2;
858
859 let message_uuid = Uuid::new_v4();
860
861 let mut server_txn = server.write(time_p1).await.unwrap();
862
863 let mut e_msg = entry_init!(
864 (Attribute::Class, EntryClass::Object.to_value()),
865 (Attribute::Class, EntryClass::OutboundMessage.to_value()),
866 (Attribute::Uuid, Value::Uuid(message_uuid)),
867 (Attribute::SendAfter, Value::DateTime(odt_p1)),
868 (Attribute::DeleteAfter, Value::DateTime(odt_p2))
869 );
870
871 e_msg.set_ava_set(
872 &Attribute::MessageTemplate,
873 ValueSetMessage::new(OutboundMessage::TestMessageV1 {
874 display_name: "testuser".into(),
875 }),
876 );
877
878 server_txn.internal_create(vec![e_msg]).unwrap();
879
880 server_txn.commit().unwrap();
881
882 let mut server_txn = server.write(time_p1).await.unwrap();
884
885 server_txn.purge_delete_after().unwrap();
886
887 let _msg = server_txn
888 .internal_search_uuid(message_uuid)
889 .expect("Message was deleted!!!");
890
891 server_txn.commit().unwrap();
892
893 let mut server_txn = server.write(time_p3).await.unwrap();
895
896 trace!(?odt_p2);
897 server_txn.purge_delete_after().unwrap();
898
899 server_txn
900 .internal_search_uuid(message_uuid)
901 .expect_err("Message is still present");
902
903 let _msg = server_txn
905 .internal_search_all_uuid(message_uuid)
906 .expect("It's not in the recycle bin!");
907
908 server_txn.commit().unwrap();
909 }
910}