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 error!("Not all candidates were correctly revived, unable to proceed");
208 return Err(OperationError::InvalidEntryState);
209 }
210
211 let memoriam_filters = candidates
213 .iter()
214 .filter_map(|entry| {
215 entry
216 .get_uuid()
217 .map(PartialValue::Uuid)
218 .map(|pv| f_eq(Attribute::InMemoriam, pv))
219 })
220 .collect::<Vec<_>>();
221
222 let memorial_candidates = self.internal_search(filter!(f_and(vec![
223 f_eq(Attribute::Class, EntryClass::Memorial.into()),
224 f_or(memoriam_filters)
225 ])))?;
226
227 if !memorial_candidates.is_empty() {
228 let memorial_map: BTreeMap<Uuid, &EntrySealedCommitted> = memorial_candidates
230 .iter()
231 .map(|entry| (entry.get_uuid(), entry.as_ref()))
232 .collect();
233
234 let mut memorial_candidate_pairs: Vec<(
238 &EntrySealedCommitted,
239 &mut EntryInvalidCommitted,
240 )> = candidates
241 .iter_mut()
242 .filter_map(|entry| {
243 entry
244 .get_uuid()
245 .and_then(|uuid| memorial_map.get(&uuid).map(|memorial| (*memorial, entry)))
246 })
247 .collect();
248
249 Plugins::run_teardown_memorials(self, &mut memorial_candidate_pairs, re).inspect_err(
251 |err| {
252 error!(?err, "Revive operation failed (plugin)");
253 },
254 )?;
255
256 let tombstone_cand = memorial_candidates
258 .iter()
259 .map(|e| {
260 e.to_tombstone(self.cid.clone())
261 .validate(&self.schema)
262 .map_err(|e| {
263 error!("Schema Violation in teardown memorials validation: {:?}", e);
264 OperationError::SchemaViolation(e)
265 })
266 .map(|e| e.seal(&self.schema))
268 })
269 .collect::<Result<Vec<_>, _>>()?;
270
271 self.be_txn
272 .modify(&self.cid, &memorial_candidates, &tombstone_cand)
273 .inspect_err(|err| {
274 error!(?err, "Teardown memorials operation failed (backend)");
275 })?;
276 };
277
278 Plugins::run_pre_modify(self, &pre_candidates, &mut candidates, &me).inspect_err(
281 |err| {
282 error!(?err, "Revive operation failed (plugin)");
283 },
284 )?;
285
286 let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
288 .into_iter()
289 .map(|e| {
290 e.validate(&self.schema)
291 .map_err(|e| {
292 admin_error!("Schema Violation {:?}", e);
293 OperationError::SchemaViolation(e)
294 })
295 .map(|e| e.seal(&self.schema))
296 })
297 .collect();
298
299 let norm_cand: Vec<Entry<_, _>> = res?;
300
301 let mut dm_mods: BTreeMap<Uuid, ModifyList<ModifyInvalid>> = Default::default();
303
304 for entry in &pre_candidates {
305 let u: Uuid = entry.get_uuid();
307
308 if let Some(riter) = entry.get_ava_as_refuuid(Attribute::RecycledDirectMemberOf) {
309 for g_uuid in riter {
310 dm_mods
311 .entry(g_uuid)
312 .and_modify(|mlist| {
313 let m = Modify::Present(Attribute::Member, Value::Refer(u));
314 mlist.push_mod(m);
315 })
316 .or_insert({
317 let m = Modify::Present(Attribute::Member, Value::Refer(u));
318 ModifyList::new_list(vec![m])
319 });
320 }
321 }
322 }
323
324 let mp = ModifyPartial {
326 norm_cand,
327 pre_candidates,
328 me: &me,
329 };
330
331 self.modify_apply(mp)?;
333
334 for (g, mods) in dm_mods {
337 let f = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(g)));
341 self.internal_modify(&f, &mods)?;
342 }
343
344 Ok(())
345 }
346
347 #[cfg(test)]
348 pub(crate) fn internal_revive_uuid(&mut self, target_uuid: Uuid) -> Result<(), OperationError> {
349 let filter = filter_rec!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
351 let f_valid = filter
352 .validate(self.get_schema())
353 .map_err(OperationError::SchemaViolation)?;
354 let re = ReviveRecycledEvent::new_internal(f_valid);
355 self.revive_recycled(&re)
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::ReviveRecycledEvent;
362 use crate::event::{CreateEvent, DeleteEvent};
363 use crate::prelude::*;
364 use crate::server::ModifyEvent;
365 use crate::server::SearchEvent;
366 use crate::server::ValueSetMessage;
367 use kanidm_proto::v1::OutboundMessage;
368 use time::OffsetDateTime;
369
370 #[qs_test]
371 async fn test_recycle_simple(server: &QueryServer) {
372 let time_p1 = duration_from_epoch_now();
374 let time_p2 = time_p1 + Duration::from_secs(RECYCLEBIN_MAX_AGE * 2);
375
376 let mut server_txn = server.write(time_p1).await.unwrap();
377 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
378
379 let filt_i_rc = filter_all!(f_eq(Attribute::Class, EntryClass::Recycled.into()));
380
381 let filt_i_ts = filter_all!(f_eq(Attribute::Class, EntryClass::Tombstone.into()));
382
383 let filt_i_per = filter_all!(f_eq(Attribute::Class, EntryClass::Person.into()));
384
385 let me_rc = ModifyEvent::new_impersonate_entry(
387 admin.clone(),
388 filt_i_rc.clone(),
389 ModifyList::new_list(vec![Modify::Present(
390 Attribute::Class,
391 EntryClass::Recycled.into(),
392 )]),
393 );
394
395 let de_rc = DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_rc.clone());
396
397 let se_rc = SearchEvent::new_ext_impersonate_entry(admin.clone(), filt_i_rc.clone());
398
399 let sre_rc = SearchEvent::new_rec_impersonate_entry(admin.clone(), filt_i_rc.clone());
400
401 let rre_rc = ReviveRecycledEvent::new_impersonate_entry(
402 admin,
403 filter_all!(f_eq(
404 Attribute::Name,
405 PartialValue::new_iname("testperson1")
406 )),
407 );
408
409 let e1 = entry_init!(
411 (Attribute::Class, EntryClass::Object.to_value()),
412 (Attribute::Class, EntryClass::Account.to_value()),
413 (Attribute::Class, EntryClass::Person.to_value()),
414 (Attribute::Name, Value::new_iname("testperson1")),
415 (
416 Attribute::Uuid,
417 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
418 ),
419 (Attribute::Description, Value::new_utf8s("testperson1")),
420 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
421 );
422
423 let e2 = entry_init!(
424 (Attribute::Class, EntryClass::Object.to_value()),
425 (Attribute::Class, EntryClass::Account.to_value()),
426 (Attribute::Class, EntryClass::Person.to_value()),
427 (Attribute::Name, Value::new_iname("testperson2")),
428 (
429 Attribute::Uuid,
430 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
431 ),
432 (Attribute::Description, Value::new_utf8s("testperson2")),
433 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
434 );
435
436 let ce = CreateEvent::new_internal(vec![e1, e2]);
437 let cr = server_txn.create(&ce);
438 assert!(cr.is_ok());
439
440 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([
442 f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
443 f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
444 ])));
445 assert!(server_txn.delete(&de_sin).is_ok());
446
447 let r1 = server_txn.search(&se_rc).expect("search failed");
449 assert!(r1.is_empty());
450
451 assert!(server_txn.delete(&de_rc).is_err());
454
455 assert!(server_txn.modify(&me_rc).is_err());
458
459 let r2 = server_txn.search(&sre_rc).expect("search failed");
461 assert_eq!(r2.len(), 2);
462
463 let r2 = server_txn
466 .internal_search(filt_i_rc.clone())
467 .expect("internal search failed");
468 assert_eq!(r2.len(), 2);
469
470 assert!(server_txn.revive_recycled(&rre_rc).is_ok());
473
474 assert!(server_txn.purge_recycled().is_ok());
476 let r3 = server_txn
477 .internal_search(filt_i_rc.clone())
478 .expect("internal search failed");
479 assert_eq!(r3.len(), 1);
480
481 assert!(server_txn.commit().is_ok());
483
484 let mut server_txn = server.write(time_p2).await.unwrap();
486
487 assert!(server_txn.purge_recycled().is_ok());
489
490 let r4 = server_txn
492 .internal_search(filt_i_rc.clone())
493 .expect("internal search failed");
494 assert!(r4.is_empty());
495
496 let r5 = server_txn
498 .internal_search(filt_i_ts.clone())
499 .expect("internal search failed");
500 assert_eq!(r5.len(), 1);
501
502 let r6 = server_txn
504 .internal_search(filt_i_per.clone())
505 .expect("internal search failed");
506 assert_eq!(r6.len(), 1);
507
508 assert!(server_txn.commit().is_ok());
509 }
510
511 #[qs_test]
513 async fn test_qs_recycle_advanced(server: &QueryServer) {
514 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
516 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
517
518 let e1 = entry_init!(
519 (Attribute::Class, EntryClass::Object.to_value()),
520 (Attribute::Class, EntryClass::Account.to_value()),
521 (Attribute::Class, EntryClass::Person.to_value()),
522 (Attribute::Name, Value::new_iname("testperson1")),
523 (
524 Attribute::Uuid,
525 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
526 ),
527 (Attribute::Description, Value::new_utf8s("testperson1")),
528 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
529 );
530 let ce = CreateEvent::new_internal(vec![e1]);
531
532 let cr = server_txn.create(&ce);
533 assert!(cr.is_ok());
534 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
536 Attribute::Name,
537 PartialValue::new_iname("testperson1")
538 )));
539 assert!(server_txn.delete(&de_sin).is_ok());
540 let filt_rc = filter_all!(f_eq(Attribute::Class, EntryClass::Recycled.into()));
542 let sre_rc = SearchEvent::new_rec_impersonate_entry(admin, filt_rc);
543 let r2 = server_txn.search(&sre_rc).expect("search failed");
544 assert_eq!(r2.len(), 1);
545
546 let cr = server_txn.create(&ce);
549 assert!(cr.is_err());
550
551 assert!(server_txn.commit().is_ok());
552 }
553
554 #[qs_test]
555 async fn test_uuid_to_star_recycle(server: &QueryServer) {
556 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
557
558 let e1 = entry_init!(
559 (Attribute::Class, EntryClass::Object.to_value()),
560 (Attribute::Class, EntryClass::Person.to_value()),
561 (Attribute::Class, EntryClass::Account.to_value()),
562 (Attribute::Name, Value::new_iname("testperson1")),
563 (
564 Attribute::Uuid,
565 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
566 ),
567 (Attribute::Description, Value::new_utf8s("testperson1")),
568 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
569 );
570
571 let tuuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
572
573 let ce = CreateEvent::new_internal(vec![e1]);
574 let cr = server_txn.create(&ce);
575 assert!(cr.is_ok());
576
577 assert_eq!(
578 server_txn.uuid_to_rdn(tuuid),
579 Ok("spn=testperson1@example.com".to_string())
580 );
581
582 assert!(
583 server_txn.uuid_to_spn(tuuid)
584 == Ok(Some(Value::new_spn_str("testperson1", "example.com")))
585 );
586
587 assert_eq!(server_txn.name_to_uuid("testperson1"), Ok(tuuid));
588
589 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
591 Attribute::Name,
592 PartialValue::new_iname("testperson1")
593 )));
594 assert!(server_txn.delete(&de_sin).is_ok());
595
596 assert!(
598 server_txn.uuid_to_rdn(tuuid)
599 == Ok("uuid=cc8e95b4-c24f-4d68-ba54-8bed76f63930".to_string())
600 );
601
602 assert_eq!(server_txn.uuid_to_spn(tuuid), Ok(None));
603
604 assert!(server_txn.name_to_uuid("testperson1").is_err());
605
606 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
608 let rre_rc = ReviveRecycledEvent::new_impersonate_entry(
609 admin,
610 filter_all!(f_eq(
611 Attribute::Name,
612 PartialValue::new_iname("testperson1")
613 )),
614 );
615 assert!(server_txn.revive_recycled(&rre_rc).is_ok());
616
617 assert_eq!(
620 server_txn.uuid_to_rdn(tuuid),
621 Ok("spn=testperson1@example.com".to_string())
622 );
623
624 assert!(
625 server_txn.uuid_to_spn(tuuid)
626 == Ok(Some(Value::new_spn_str("testperson1", "example.com")))
627 );
628
629 assert_eq!(server_txn.name_to_uuid("testperson1"), Ok(tuuid));
630 }
631
632 #[qs_test]
633 async fn test_tombstone(server: &QueryServer) {
634 let time_p1 = duration_from_epoch_now();
636 let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
637 let time_p3 = time_p2 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
638
639 trace!("test_tombstone_start");
640 let mut server_txn = server.write(time_p1).await.unwrap();
641 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
642
643 let filt_i_ts = filter_all!(f_eq(Attribute::Class, EntryClass::Tombstone.into()));
644
645 let me_ts = ModifyEvent::new_impersonate_entry(
648 admin.clone(),
649 filt_i_ts.clone(),
650 ModifyList::new_list(vec![Modify::Present(
651 Attribute::Class,
652 EntryClass::Tombstone.into(),
653 )]),
654 );
655
656 let de_ts = DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_ts.clone());
657 let se_ts = SearchEvent::new_ext_impersonate_entry(admin, filt_i_ts.clone());
658
659 let e_ts = entry_init!(
661 (Attribute::Class, EntryClass::Object.to_value()),
662 (Attribute::Class, EntryClass::Account.to_value()),
663 (Attribute::Class, EntryClass::Person.to_value()),
664 (Attribute::Name, Value::new_iname("testperson1")),
665 (
666 Attribute::Uuid,
667 Value::Uuid(uuid!("9557f49c-97a5-4277-a9a5-097d17eb8317"))
668 ),
669 (Attribute::Description, Value::new_utf8s("testperson1")),
670 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
671 );
672
673 let ce = CreateEvent::new_internal(vec![e_ts]);
674 let cr = server_txn.create(&ce);
675 assert!(cr.is_ok());
676
677 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([f_eq(
678 Attribute::Name,
679 PartialValue::new_iname("testperson1")
680 )])));
681 assert!(server_txn.delete(&de_sin).is_ok());
682
683 assert!(server_txn.commit().is_ok());
685
686 let mut server_txn = server.write(time_p2).await.unwrap();
688 assert!(server_txn.purge_recycled().is_ok());
689
690 let r1 = server_txn.search(&se_ts).expect("search failed");
694 assert!(r1.is_empty());
695
696 assert!(server_txn.delete(&de_ts).is_err());
699
700 assert!(server_txn.modify(&me_ts).is_err());
703
704 let r2 = server_txn
707 .internal_search(filt_i_ts.clone())
708 .expect("internal search failed");
709 assert_eq!(r2.len(), 1);
710
711 assert!(server_txn.purge_tombstones().is_ok());
713
714 let r3 = server_txn
715 .internal_search(filt_i_ts.clone())
716 .expect("internal search failed");
717 assert_eq!(r3.len(), 1);
718
719 assert!(server_txn.commit().is_ok());
721
722 let mut server_txn = server.write(time_p3).await.unwrap();
724
725 assert!(server_txn.purge_tombstones().is_ok());
727
728 let r4 = server_txn
731 .internal_search(filt_i_ts)
732 .expect("internal search failed");
733 assert!(r4.is_empty());
734
735 assert!(server_txn.commit().is_ok());
736 }
737
738 fn create_user(name: &str, uuid: &str) -> Entry<EntryInit, EntryNew> {
739 entry_init!(
740 (Attribute::Class, EntryClass::Object.to_value()),
741 (Attribute::Class, EntryClass::Account.to_value()),
742 (Attribute::Class, EntryClass::Person.to_value()),
743 (Attribute::Name, Value::new_iname(name)),
744 (
745 Attribute::Uuid,
746 #[allow(clippy::panic)]
747 Value::new_uuid_s(uuid).unwrap_or_else(|| { panic!("{}", Attribute::Uuid) })
748 ),
749 (Attribute::Description, Value::new_utf8s("testperson-entry")),
750 (Attribute::DisplayName, Value::new_utf8s(name))
751 )
752 }
753
754 fn create_group(name: &str, uuid: &str, members: &[&str]) -> Entry<EntryInit, EntryNew> {
755 #[allow(clippy::panic)]
756 let mut e1 = entry_init!(
757 (Attribute::Class, EntryClass::Object.to_value()),
758 (Attribute::Class, EntryClass::Group.to_value()),
759 (Attribute::Name, Value::new_iname(name)),
760 (
761 Attribute::Uuid,
762 Value::new_uuid_s(uuid).unwrap_or_else(|| { panic!("{}", Attribute::Uuid) })
763 ),
764 (Attribute::Description, Value::new_utf8s("testgroup-entry"))
765 );
766 members
767 .iter()
768 .for_each(|m| e1.add_ava(Attribute::Member, Value::new_refer_s(m).unwrap()));
769 e1
770 }
771
772 fn check_entry_has_mo(qs: &mut QueryServerWriteTransaction, name: &str, mo: &str) -> bool {
773 let entry = qs
774 .internal_search(filter!(f_eq(
775 Attribute::Name,
776 PartialValue::new_iname(name)
777 )))
778 .unwrap()
779 .pop()
780 .unwrap();
781
782 trace!(?entry);
783
784 entry.attribute_equality(Attribute::MemberOf, &PartialValue::new_refer_s(mo).unwrap())
785 }
786
787 #[qs_test]
788 async fn test_revive_advanced_directmemberships(server: &QueryServer) {
789 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
791 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
792
793 let u1 = create_user("u1", "22b47373-d123-421f-859e-9ddd8ab14a2a");
795 let g1 = create_group(
796 "g1",
797 "cca2bbfc-5b43-43f3-be9e-f5b03b3defec",
798 &["22b47373-d123-421f-859e-9ddd8ab14a2a"],
799 );
800
801 let u2 = create_user("u2", "5c19a4a2-b9f0-4429-b130-5782de5fddda");
803 let g2a = create_group(
804 "g2a",
805 "e44cf9cd-9941-44cb-a02f-307b6e15ac54",
806 &["5c19a4a2-b9f0-4429-b130-5782de5fddda"],
807 );
808 let g2b = create_group(
809 "g2b",
810 "d3132e6e-18ce-4b87-bee1-1d25e4bfe96d",
811 &["e44cf9cd-9941-44cb-a02f-307b6e15ac54"],
812 );
813
814 let u3 = create_user("u3", "68467a41-6e8e-44d0-9214-a5164e75ca03");
816 let g3 = create_group(
817 "g3",
818 "36048117-e479-45ed-aeb5-611e8d83d5b1",
819 &["68467a41-6e8e-44d0-9214-a5164e75ca03"],
820 );
821
822 let u4 = create_user("u4", "d696b10f-1729-4f1a-83d0-ca06525c2f59");
825 let g4 = create_group(
826 "g4",
827 "d5c59ac6-c533-4b00-989f-d0e183f07bab",
828 &["d696b10f-1729-4f1a-83d0-ca06525c2f59"],
829 );
830
831 let ce = CreateEvent::new_internal(vec![u1, g1, u2, g2a, g2b, u3, g3, u4, g4]);
832 let cr = server_txn.create(&ce);
833 assert!(cr.is_ok());
834
835 let de = DeleteEvent::new_internal_invalid(filter!(f_or(vec![
837 f_eq(Attribute::Name, PartialValue::new_iname("u1")),
838 f_eq(Attribute::Name, PartialValue::new_iname("u2")),
839 f_eq(Attribute::Name, PartialValue::new_iname("u3")),
840 f_eq(Attribute::Name, PartialValue::new_iname("g3")),
841 f_eq(Attribute::Name, PartialValue::new_iname("u4")),
842 f_eq(Attribute::Name, PartialValue::new_iname("g4"))
843 ])));
844 assert!(server_txn.delete(&de).is_ok());
845
846 let rev1 = ReviveRecycledEvent::new_impersonate_entry(
848 admin.clone(),
849 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u1"))),
850 );
851 assert!(server_txn.revive_recycled(&rev1).is_ok());
852 assert!(check_entry_has_mo(
854 &mut server_txn,
855 "u1",
856 "cca2bbfc-5b43-43f3-be9e-f5b03b3defec"
857 ));
858
859 let rev2 = ReviveRecycledEvent::new_impersonate_entry(
861 admin.clone(),
862 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u2"))),
863 );
864 assert!(server_txn.revive_recycled(&rev2).is_ok());
865 assert!(check_entry_has_mo(
866 &mut server_txn,
867 "u2",
868 "e44cf9cd-9941-44cb-a02f-307b6e15ac54"
869 ));
870 assert!(check_entry_has_mo(
871 &mut server_txn,
872 "u2",
873 "d3132e6e-18ce-4b87-bee1-1d25e4bfe96d"
874 ));
875
876 let rev3 = ReviveRecycledEvent::new_impersonate_entry(
878 admin.clone(),
879 filter_all!(f_or(vec![
880 f_eq(Attribute::Name, PartialValue::new_iname("u3")),
881 f_eq(Attribute::Name, PartialValue::new_iname("g3"))
882 ])),
883 );
884 assert!(server_txn.revive_recycled(&rev3).is_ok());
885 assert!(!check_entry_has_mo(
886 &mut server_txn,
887 "u3",
888 "36048117-e479-45ed-aeb5-611e8d83d5b1"
889 ));
890
891 let rev4a = ReviveRecycledEvent::new_impersonate_entry(
893 admin.clone(),
894 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u4"))),
895 );
896 assert!(server_txn.revive_recycled(&rev4a).is_ok());
897 assert!(!check_entry_has_mo(
898 &mut server_txn,
899 "u4",
900 "d5c59ac6-c533-4b00-989f-d0e183f07bab"
901 ));
902
903 let rev4b = ReviveRecycledEvent::new_impersonate_entry(
905 admin,
906 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("g4"))),
907 );
908 assert!(server_txn.revive_recycled(&rev4b).is_ok());
909 assert!(!check_entry_has_mo(
910 &mut server_txn,
911 "u4",
912 "d5c59ac6-c533-4b00-989f-d0e183f07bab"
913 ));
914
915 assert!(server_txn.commit().is_ok());
916 }
917
918 #[qs_test]
919 async fn test_entry_delete_after(server: &QueryServer) {
920 let time_p1 = duration_from_epoch_now();
921 let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
922 let time_p3 = time_p2 + Duration::from_secs(1);
923
924 let odt_p1 = OffsetDateTime::UNIX_EPOCH + time_p1;
925 let odt_p2 = OffsetDateTime::UNIX_EPOCH + time_p2;
926
927 let message_uuid = Uuid::new_v4();
928
929 let mut server_txn = server.write(time_p1).await.unwrap();
930
931 let mut e_msg = entry_init!(
932 (Attribute::Class, EntryClass::Object.to_value()),
933 (Attribute::Class, EntryClass::OutboundMessage.to_value()),
934 (Attribute::Uuid, Value::Uuid(message_uuid)),
935 (Attribute::SendAfter, Value::DateTime(odt_p1)),
936 (Attribute::DeleteAfter, Value::DateTime(odt_p2))
937 );
938
939 e_msg.set_ava_set(
940 &Attribute::MessageTemplate,
941 ValueSetMessage::new(OutboundMessage::TestMessageV1 {
942 display_name: "testuser".into(),
943 }),
944 );
945
946 server_txn.internal_create(vec![e_msg]).unwrap();
947
948 server_txn.commit().unwrap();
949
950 let mut server_txn = server.write(time_p1).await.unwrap();
952
953 server_txn.purge_delete_after().unwrap();
954
955 let _msg = server_txn
956 .internal_search_uuid(message_uuid)
957 .expect("Message was deleted!!!");
958
959 server_txn.commit().unwrap();
960
961 let mut server_txn = server.write(time_p3).await.unwrap();
963
964 trace!(?odt_p2);
965 server_txn.purge_delete_after().unwrap();
966
967 server_txn
968 .internal_search_uuid(message_uuid)
969 .expect_err("Message is still present");
970
971 let _msg = server_txn
973 .internal_search_all_uuid(message_uuid)
974 .expect("It's not in the recycle bin!");
975
976 server_txn.commit().unwrap();
977 }
978}