1use super::modify::ModifyPartial;
2use crate::event::ReviveRecycledEvent;
3use crate::prelude::*;
4use crate::server::Plugins;
5use hashbrown::HashMap;
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 revive_recycled(&mut self, re: &ReviveRecycledEvent) -> Result<(), OperationError> {
79 if !re.ident.is_internal() {
85 security_info!(name = %re.ident, "revive initiator");
86 }
87
88 let pre_candidates =
90 self.impersonate_search_valid(re.filter.clone(), re.filter.clone(), &re.ident)?;
91
92 if pre_candidates.is_empty() {
94 if re.ident.is_internal() {
95 trace!(
96 "revive: no candidates match filter ... continuing {:?}",
97 re.filter
98 );
99 return Ok(());
100 } else {
101 request_error!(
102 "revive: no candidates match filter, failure {:?}",
103 re.filter
104 );
105 return Err(OperationError::NoMatchingEntries);
106 }
107 };
108
109 trace!("revive: pre_candidates -> {:?}", pre_candidates);
110
111 let modlist = ModifyList::new_list(vec![Modify::Removed(
113 Attribute::Class,
114 EntryClass::Recycled.into(),
115 )]);
116
117 let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
118 admin_error!("revive recycled modlist Schema Violation {:?}", e);
119 OperationError::SchemaViolation(e)
120 })?;
121
122 let me =
123 ModifyEvent::new_impersonate(&re.ident, re.filter.clone(), re.filter.clone(), m_valid);
124
125 let access = self.get_accesscontrols();
126 let op_allow = access
127 .modify_allow_operation(&me, &pre_candidates)
128 .map_err(|e| {
129 admin_error!("Unable to check modify access {:?}", e);
130 e
131 })?;
132 if !op_allow {
133 return Err(OperationError::AccessDenied);
134 }
135
136 if pre_candidates.iter().all(|e| e.mask_recycled().is_some()) {
138 admin_warn!("Refusing to revive entries that are already live!");
139 return Err(OperationError::AccessDenied);
140 }
141
142 let mut dm_mods: HashMap<Uuid, ModifyList<ModifyInvalid>> =
144 HashMap::with_capacity(pre_candidates.len());
145
146 for e in &pre_candidates {
147 let u: Uuid = e.get_uuid();
149
150 if let Some(riter) = e.get_ava_as_refuuid(Attribute::RecycledDirectMemberOf) {
151 for g_uuid in riter {
152 dm_mods
153 .entry(g_uuid)
154 .and_modify(|mlist| {
155 let m = Modify::Present(Attribute::Member, Value::Refer(u));
156 mlist.push_mod(m);
157 })
158 .or_insert({
159 let m = Modify::Present(Attribute::Member, Value::Refer(u));
160 ModifyList::new_list(vec![m])
161 });
162 }
163 }
164 }
165
166 let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
168 .iter()
169 .map(|er| {
170 er.as_ref()
171 .clone()
172 .invalidate(self.cid.clone(), &self.trim_cid)
173 })
174 .map(|er| er.to_revived())
176 .collect();
177
178 if candidates.iter().all(|e| e.mask_recycled().is_none()) {
180 admin_error!("Not all candidates were correctly revived, unable to proceed");
181 return Err(OperationError::InvalidEntryState);
182 }
183
184 Plugins::run_pre_modify(self, &pre_candidates, &mut candidates, &me).map_err(|e| {
187 admin_error!("Revive operation failed (plugin), {:?}", e);
188 e
189 })?;
190
191 let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
193 .into_iter()
194 .map(|e| {
195 e.validate(&self.schema)
196 .map_err(|e| {
197 admin_error!("Schema Violation {:?}", e);
198 OperationError::SchemaViolation(e)
199 })
200 .map(|e| e.seal(&self.schema))
201 })
202 .collect();
203
204 let norm_cand: Vec<Entry<_, _>> = res?;
205
206 let mp = ModifyPartial {
208 norm_cand,
209 pre_candidates,
210 me: &me,
211 };
212
213 self.modify_apply(mp)?;
215
216 for (g, mods) in dm_mods {
219 let f = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(g)));
223 self.internal_modify(&f, &mods)?;
224 }
225
226 Ok(())
227 }
228
229 #[cfg(test)]
230 pub(crate) fn internal_revive_uuid(&mut self, target_uuid: Uuid) -> Result<(), OperationError> {
231 let filter = filter_rec!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
233 let f_valid = filter
234 .validate(self.get_schema())
235 .map_err(OperationError::SchemaViolation)?;
236 let re = ReviveRecycledEvent::new_internal(f_valid);
237 self.revive_recycled(&re)
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use crate::prelude::*;
244
245 use crate::event::{CreateEvent, DeleteEvent};
246 use crate::server::ModifyEvent;
247 use crate::server::SearchEvent;
248
249 use super::ReviveRecycledEvent;
250
251 #[qs_test]
252 async fn test_recycle_simple(server: &QueryServer) {
253 let time_p1 = duration_from_epoch_now();
255 let time_p2 = time_p1 + Duration::from_secs(RECYCLEBIN_MAX_AGE * 2);
256
257 let mut server_txn = server.write(time_p1).await.unwrap();
258 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
259
260 let filt_i_rc = filter_all!(f_eq(Attribute::Class, EntryClass::Recycled.into()));
261
262 let filt_i_ts = filter_all!(f_eq(Attribute::Class, EntryClass::Tombstone.into()));
263
264 let filt_i_per = filter_all!(f_eq(Attribute::Class, EntryClass::Person.into()));
265
266 let me_rc = ModifyEvent::new_impersonate_entry(
268 admin.clone(),
269 filt_i_rc.clone(),
270 ModifyList::new_list(vec![Modify::Present(
271 Attribute::Class,
272 EntryClass::Recycled.into(),
273 )]),
274 );
275
276 let de_rc = DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_rc.clone());
277
278 let se_rc = SearchEvent::new_ext_impersonate_entry(admin.clone(), filt_i_rc.clone());
279
280 let sre_rc = SearchEvent::new_rec_impersonate_entry(admin.clone(), filt_i_rc.clone());
281
282 let rre_rc = ReviveRecycledEvent::new_impersonate_entry(
283 admin,
284 filter_all!(f_eq(
285 Attribute::Name,
286 PartialValue::new_iname("testperson1")
287 )),
288 );
289
290 let e1 = entry_init!(
292 (Attribute::Class, EntryClass::Object.to_value()),
293 (Attribute::Class, EntryClass::Account.to_value()),
294 (Attribute::Class, EntryClass::Person.to_value()),
295 (Attribute::Name, Value::new_iname("testperson1")),
296 (
297 Attribute::Uuid,
298 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
299 ),
300 (Attribute::Description, Value::new_utf8s("testperson1")),
301 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
302 );
303
304 let e2 = entry_init!(
305 (Attribute::Class, EntryClass::Object.to_value()),
306 (Attribute::Class, EntryClass::Account.to_value()),
307 (Attribute::Class, EntryClass::Person.to_value()),
308 (Attribute::Name, Value::new_iname("testperson2")),
309 (
310 Attribute::Uuid,
311 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
312 ),
313 (Attribute::Description, Value::new_utf8s("testperson2")),
314 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
315 );
316
317 let ce = CreateEvent::new_internal(vec![e1, e2]);
318 let cr = server_txn.create(&ce);
319 assert!(cr.is_ok());
320
321 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([
323 f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
324 f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
325 ])));
326 assert!(server_txn.delete(&de_sin).is_ok());
327
328 let r1 = server_txn.search(&se_rc).expect("search failed");
330 assert!(r1.is_empty());
331
332 assert!(server_txn.delete(&de_rc).is_err());
335
336 assert!(server_txn.modify(&me_rc).is_err());
339
340 let r2 = server_txn.search(&sre_rc).expect("search failed");
342 assert_eq!(r2.len(), 2);
343
344 let r2 = server_txn
347 .internal_search(filt_i_rc.clone())
348 .expect("internal search failed");
349 assert_eq!(r2.len(), 2);
350
351 assert!(server_txn.revive_recycled(&rre_rc).is_ok());
354
355 assert!(server_txn.purge_recycled().is_ok());
357 let r3 = server_txn
358 .internal_search(filt_i_rc.clone())
359 .expect("internal search failed");
360 assert_eq!(r3.len(), 1);
361
362 assert!(server_txn.commit().is_ok());
364
365 let mut server_txn = server.write(time_p2).await.unwrap();
367
368 assert!(server_txn.purge_recycled().is_ok());
370
371 let r4 = server_txn
373 .internal_search(filt_i_rc.clone())
374 .expect("internal search failed");
375 assert!(r4.is_empty());
376
377 let r5 = server_txn
379 .internal_search(filt_i_ts.clone())
380 .expect("internal search failed");
381 assert_eq!(r5.len(), 1);
382
383 let r6 = server_txn
385 .internal_search(filt_i_per.clone())
386 .expect("internal search failed");
387 assert_eq!(r6.len(), 1);
388
389 assert!(server_txn.commit().is_ok());
390 }
391
392 #[qs_test]
394 async fn test_qs_recycle_advanced(server: &QueryServer) {
395 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
397 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
398
399 let e1 = entry_init!(
400 (Attribute::Class, EntryClass::Object.to_value()),
401 (Attribute::Class, EntryClass::Account.to_value()),
402 (Attribute::Class, EntryClass::Person.to_value()),
403 (Attribute::Name, Value::new_iname("testperson1")),
404 (
405 Attribute::Uuid,
406 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
407 ),
408 (Attribute::Description, Value::new_utf8s("testperson1")),
409 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
410 );
411 let ce = CreateEvent::new_internal(vec![e1]);
412
413 let cr = server_txn.create(&ce);
414 assert!(cr.is_ok());
415 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
417 Attribute::Name,
418 PartialValue::new_iname("testperson1")
419 )));
420 assert!(server_txn.delete(&de_sin).is_ok());
421 let filt_rc = filter_all!(f_eq(Attribute::Class, EntryClass::Recycled.into()));
423 let sre_rc = SearchEvent::new_rec_impersonate_entry(admin, filt_rc);
424 let r2 = server_txn.search(&sre_rc).expect("search failed");
425 assert_eq!(r2.len(), 1);
426
427 let cr = server_txn.create(&ce);
430 assert!(cr.is_err());
431
432 assert!(server_txn.commit().is_ok());
433 }
434
435 #[qs_test]
436 async fn test_uuid_to_star_recycle(server: &QueryServer) {
437 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
438
439 let e1 = entry_init!(
440 (Attribute::Class, EntryClass::Object.to_value()),
441 (Attribute::Class, EntryClass::Person.to_value()),
442 (Attribute::Class, EntryClass::Account.to_value()),
443 (Attribute::Name, Value::new_iname("testperson1")),
444 (
445 Attribute::Uuid,
446 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
447 ),
448 (Attribute::Description, Value::new_utf8s("testperson1")),
449 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
450 );
451
452 let tuuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
453
454 let ce = CreateEvent::new_internal(vec![e1]);
455 let cr = server_txn.create(&ce);
456 assert!(cr.is_ok());
457
458 assert_eq!(
459 server_txn.uuid_to_rdn(tuuid),
460 Ok("spn=testperson1@example.com".to_string())
461 );
462
463 assert!(
464 server_txn.uuid_to_spn(tuuid)
465 == Ok(Some(Value::new_spn_str("testperson1", "example.com")))
466 );
467
468 assert_eq!(server_txn.name_to_uuid("testperson1"), Ok(tuuid));
469
470 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_eq(
472 Attribute::Name,
473 PartialValue::new_iname("testperson1")
474 )));
475 assert!(server_txn.delete(&de_sin).is_ok());
476
477 assert!(
479 server_txn.uuid_to_rdn(tuuid)
480 == Ok("uuid=cc8e95b4-c24f-4d68-ba54-8bed76f63930".to_string())
481 );
482
483 assert_eq!(server_txn.uuid_to_spn(tuuid), Ok(None));
484
485 assert!(server_txn.name_to_uuid("testperson1").is_err());
486
487 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
489 let rre_rc = ReviveRecycledEvent::new_impersonate_entry(
490 admin,
491 filter_all!(f_eq(
492 Attribute::Name,
493 PartialValue::new_iname("testperson1")
494 )),
495 );
496 assert!(server_txn.revive_recycled(&rre_rc).is_ok());
497
498 assert_eq!(
501 server_txn.uuid_to_rdn(tuuid),
502 Ok("spn=testperson1@example.com".to_string())
503 );
504
505 assert!(
506 server_txn.uuid_to_spn(tuuid)
507 == Ok(Some(Value::new_spn_str("testperson1", "example.com")))
508 );
509
510 assert_eq!(server_txn.name_to_uuid("testperson1"), Ok(tuuid));
511 }
512
513 #[qs_test]
514 async fn test_tombstone(server: &QueryServer) {
515 let time_p1 = duration_from_epoch_now();
517 let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
518 let time_p3 = time_p2 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
519
520 trace!("test_tombstone_start");
521 let mut server_txn = server.write(time_p1).await.unwrap();
522 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
523
524 let filt_i_ts = filter_all!(f_eq(Attribute::Class, EntryClass::Tombstone.into()));
525
526 let me_ts = ModifyEvent::new_impersonate_entry(
529 admin.clone(),
530 filt_i_ts.clone(),
531 ModifyList::new_list(vec![Modify::Present(
532 Attribute::Class,
533 EntryClass::Tombstone.into(),
534 )]),
535 );
536
537 let de_ts = DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_ts.clone());
538 let se_ts = SearchEvent::new_ext_impersonate_entry(admin, filt_i_ts.clone());
539
540 let e_ts = entry_init!(
542 (Attribute::Class, EntryClass::Object.to_value()),
543 (Attribute::Class, EntryClass::Account.to_value()),
544 (Attribute::Class, EntryClass::Person.to_value()),
545 (Attribute::Name, Value::new_iname("testperson1")),
546 (
547 Attribute::Uuid,
548 Value::Uuid(uuid!("9557f49c-97a5-4277-a9a5-097d17eb8317"))
549 ),
550 (Attribute::Description, Value::new_utf8s("testperson1")),
551 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
552 );
553
554 let ce = CreateEvent::new_internal(vec![e_ts]);
555 let cr = server_txn.create(&ce);
556 assert!(cr.is_ok());
557
558 let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([f_eq(
559 Attribute::Name,
560 PartialValue::new_iname("testperson1")
561 )])));
562 assert!(server_txn.delete(&de_sin).is_ok());
563
564 assert!(server_txn.commit().is_ok());
566
567 let mut server_txn = server.write(time_p2).await.unwrap();
569 assert!(server_txn.purge_recycled().is_ok());
570
571 let r1 = server_txn.search(&se_ts).expect("search failed");
575 assert!(r1.is_empty());
576
577 assert!(server_txn.delete(&de_ts).is_err());
580
581 assert!(server_txn.modify(&me_ts).is_err());
584
585 let r2 = server_txn
588 .internal_search(filt_i_ts.clone())
589 .expect("internal search failed");
590 assert_eq!(r2.len(), 1);
591
592 assert!(server_txn.purge_tombstones().is_ok());
594
595 let r3 = server_txn
596 .internal_search(filt_i_ts.clone())
597 .expect("internal search failed");
598 assert_eq!(r3.len(), 1);
599
600 assert!(server_txn.commit().is_ok());
602
603 let mut server_txn = server.write(time_p3).await.unwrap();
605
606 assert!(server_txn.purge_tombstones().is_ok());
608
609 let r4 = server_txn
612 .internal_search(filt_i_ts)
613 .expect("internal search failed");
614 assert!(r4.is_empty());
615
616 assert!(server_txn.commit().is_ok());
617 }
618
619 fn create_user(name: &str, uuid: &str) -> Entry<EntryInit, EntryNew> {
620 entry_init!(
621 (Attribute::Class, EntryClass::Object.to_value()),
622 (Attribute::Class, EntryClass::Account.to_value()),
623 (Attribute::Class, EntryClass::Person.to_value()),
624 (Attribute::Name, Value::new_iname(name)),
625 (
626 Attribute::Uuid,
627 #[allow(clippy::panic)]
628 Value::new_uuid_s(uuid).unwrap_or_else(|| { panic!("{}", Attribute::Uuid) })
629 ),
630 (Attribute::Description, Value::new_utf8s("testperson-entry")),
631 (Attribute::DisplayName, Value::new_utf8s(name))
632 )
633 }
634
635 fn create_group(name: &str, uuid: &str, members: &[&str]) -> Entry<EntryInit, EntryNew> {
636 #[allow(clippy::panic)]
637 let mut e1 = entry_init!(
638 (Attribute::Class, EntryClass::Object.to_value()),
639 (Attribute::Class, EntryClass::Group.to_value()),
640 (Attribute::Name, Value::new_iname(name)),
641 (
642 Attribute::Uuid,
643 Value::new_uuid_s(uuid).unwrap_or_else(|| { panic!("{}", Attribute::Uuid) })
644 ),
645 (Attribute::Description, Value::new_utf8s("testgroup-entry"))
646 );
647 members
648 .iter()
649 .for_each(|m| e1.add_ava(Attribute::Member, Value::new_refer_s(m).unwrap()));
650 e1
651 }
652
653 fn check_entry_has_mo(qs: &mut QueryServerWriteTransaction, name: &str, mo: &str) -> bool {
654 let entry = qs
655 .internal_search(filter!(f_eq(
656 Attribute::Name,
657 PartialValue::new_iname(name)
658 )))
659 .unwrap()
660 .pop()
661 .unwrap();
662
663 trace!(?entry);
664
665 entry.attribute_equality(Attribute::MemberOf, &PartialValue::new_refer_s(mo).unwrap())
666 }
667
668 #[qs_test]
669 async fn test_revive_advanced_directmemberships(server: &QueryServer) {
670 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
672 let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
673
674 let u1 = create_user("u1", "22b47373-d123-421f-859e-9ddd8ab14a2a");
676 let g1 = create_group(
677 "g1",
678 "cca2bbfc-5b43-43f3-be9e-f5b03b3defec",
679 &["22b47373-d123-421f-859e-9ddd8ab14a2a"],
680 );
681
682 let u2 = create_user("u2", "5c19a4a2-b9f0-4429-b130-5782de5fddda");
684 let g2a = create_group(
685 "g2a",
686 "e44cf9cd-9941-44cb-a02f-307b6e15ac54",
687 &["5c19a4a2-b9f0-4429-b130-5782de5fddda"],
688 );
689 let g2b = create_group(
690 "g2b",
691 "d3132e6e-18ce-4b87-bee1-1d25e4bfe96d",
692 &["e44cf9cd-9941-44cb-a02f-307b6e15ac54"],
693 );
694
695 let u3 = create_user("u3", "68467a41-6e8e-44d0-9214-a5164e75ca03");
697 let g3 = create_group(
698 "g3",
699 "36048117-e479-45ed-aeb5-611e8d83d5b1",
700 &["68467a41-6e8e-44d0-9214-a5164e75ca03"],
701 );
702
703 let u4 = create_user("u4", "d696b10f-1729-4f1a-83d0-ca06525c2f59");
706 let g4 = create_group(
707 "g4",
708 "d5c59ac6-c533-4b00-989f-d0e183f07bab",
709 &["d696b10f-1729-4f1a-83d0-ca06525c2f59"],
710 );
711
712 let ce = CreateEvent::new_internal(vec![u1, g1, u2, g2a, g2b, u3, g3, u4, g4]);
713 let cr = server_txn.create(&ce);
714 assert!(cr.is_ok());
715
716 let de = DeleteEvent::new_internal_invalid(filter!(f_or(vec![
718 f_eq(Attribute::Name, PartialValue::new_iname("u1")),
719 f_eq(Attribute::Name, PartialValue::new_iname("u2")),
720 f_eq(Attribute::Name, PartialValue::new_iname("u3")),
721 f_eq(Attribute::Name, PartialValue::new_iname("g3")),
722 f_eq(Attribute::Name, PartialValue::new_iname("u4")),
723 f_eq(Attribute::Name, PartialValue::new_iname("g4"))
724 ])));
725 assert!(server_txn.delete(&de).is_ok());
726
727 let rev1 = ReviveRecycledEvent::new_impersonate_entry(
729 admin.clone(),
730 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u1"))),
731 );
732 assert!(server_txn.revive_recycled(&rev1).is_ok());
733 assert!(check_entry_has_mo(
735 &mut server_txn,
736 "u1",
737 "cca2bbfc-5b43-43f3-be9e-f5b03b3defec"
738 ));
739
740 let rev2 = ReviveRecycledEvent::new_impersonate_entry(
742 admin.clone(),
743 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u2"))),
744 );
745 assert!(server_txn.revive_recycled(&rev2).is_ok());
746 assert!(check_entry_has_mo(
747 &mut server_txn,
748 "u2",
749 "e44cf9cd-9941-44cb-a02f-307b6e15ac54"
750 ));
751 assert!(check_entry_has_mo(
752 &mut server_txn,
753 "u2",
754 "d3132e6e-18ce-4b87-bee1-1d25e4bfe96d"
755 ));
756
757 let rev3 = ReviveRecycledEvent::new_impersonate_entry(
759 admin.clone(),
760 filter_all!(f_or(vec![
761 f_eq(Attribute::Name, PartialValue::new_iname("u3")),
762 f_eq(Attribute::Name, PartialValue::new_iname("g3"))
763 ])),
764 );
765 assert!(server_txn.revive_recycled(&rev3).is_ok());
766 assert!(!check_entry_has_mo(
767 &mut server_txn,
768 "u3",
769 "36048117-e479-45ed-aeb5-611e8d83d5b1"
770 ));
771
772 let rev4a = ReviveRecycledEvent::new_impersonate_entry(
774 admin.clone(),
775 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("u4"))),
776 );
777 assert!(server_txn.revive_recycled(&rev4a).is_ok());
778 assert!(!check_entry_has_mo(
779 &mut server_txn,
780 "u4",
781 "d5c59ac6-c533-4b00-989f-d0e183f07bab"
782 ));
783
784 let rev4b = ReviveRecycledEvent::new_impersonate_entry(
786 admin,
787 filter_all!(f_eq(Attribute::Name, PartialValue::new_iname("g4"))),
788 );
789 assert!(server_txn.revive_recycled(&rev4b).is_ok());
790 assert!(!check_entry_has_mo(
791 &mut server_txn,
792 "u4",
793 "d5c59ac6-c533-4b00-989f-d0e183f07bab"
794 ));
795
796 assert!(server_txn.commit().is_ok());
797 }
798}