1use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
13use crate::plugins::Plugin;
14use crate::prelude::*;
15use crate::schema::{SchemaAttribute, SchemaTransaction};
16use hashbrown::{HashMap, HashSet};
17use std::collections::BTreeSet;
18use std::sync::Arc;
19
20pub struct ReferentialIntegrity;
21
22impl ReferentialIntegrity {
23    #[instrument(level = "debug", name = "check_uuids_exist_fast", skip_all)]
24    fn check_uuids_exist_fast(
25        qs: &mut QueryServerWriteTransaction,
26        inner: &[Uuid],
27    ) -> Result<bool, OperationError> {
28        if inner.is_empty() {
29            trace!("no reference types modified, skipping check");
31            return Ok(true);
32        }
33
34        let inner: Vec<_> = inner
35            .iter()
36            .map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(*u)))
37            .collect();
38
39        let filt_in = filter!(f_inc(inner));
43        let b = qs.internal_exists(&filt_in).inspect_err(|err| {
44            error!(?err, filter = ?filt_in, "internal exists failure");
45        })?;
46
47        if b {
49            Ok(true)
50        } else {
51            Ok(false)
52        }
53    }
54
55    #[instrument(level = "debug", name = "check_uuids_exist_slow", skip_all)]
56    fn check_uuids_exist_slow(
57        qs: &mut QueryServerWriteTransaction,
58        inner: &[Uuid],
59    ) -> Result<Vec<Uuid>, OperationError> {
60        if inner.is_empty() {
61            trace!("no reference types modified, skipping check");
64            return Ok(Vec::with_capacity(0));
65        }
66
67        let mut missing = Vec::with_capacity(inner.len());
68        for u in inner {
69            let filt_in = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(*u)));
70            let b = qs.internal_exists(&filt_in).inspect_err(|err| {
71                error!(?err, filter = ?filt_in, "internal exists failure");
72            })?;
73
74            if !b {
76                missing.push(*u)
77            }
78        }
79
80        Ok(missing)
81    }
82
83    fn remove_references(
89        qs: &mut QueryServerWriteTransaction,
90        uuids: Vec<Uuid>,
91    ) -> Result<(), OperationError> {
92        trace!(?uuids);
93
94        let schema = qs.get_schema();
96        let ref_types = schema.get_reference_types();
97
98        let removed_ids: BTreeSet<_> = uuids.iter().map(|u| PartialValue::Refer(*u)).collect();
99
100        let filt = filter_all!(f_or(
104            uuids
105                .into_iter()
106                .flat_map(|u| ref_types
107                    .values()
108                    .map(move |r_type| { f_eq(r_type.name.clone(), PartialValue::Refer(u)) }))
109                .collect(),
110        ));
111
112        trace!("refint post_delete filter {:?}", filt);
113
114        let mut work_set = qs.internal_search_writeable(&filt)?;
115
116        for (_, post) in work_set.iter_mut() {
117            for schema_attribute in ref_types.values() {
118                post.remove_avas(&schema_attribute.name, &removed_ids);
119            }
120        }
121
122        qs.internal_apply_writable(work_set)
123    }
124
125    #[instrument(level = "debug", skip_all)]
126    fn check_refers_to_target_loop_fast(
127        qs: &mut QueryServerWriteTransaction,
128        inner: &[Uuid],
129    ) -> Result<bool, OperationError> {
130        if inner.is_empty() {
131            trace!("no refers types modified, skipping check");
133            return Ok(false);
134        }
135
136        let inner: Vec<_> = inner
137            .iter()
138            .map(|u| {
139                f_and(vec![
140                    f_eq(Attribute::Uuid, PartialValue::Uuid(*u)),
141                    f_andnot(f_pres(Attribute::Refers)),
142                ])
143            })
144            .collect();
145
146        let filt_in = filter!(f_inc(inner));
150
151        let b = qs.internal_exists(&filt_in).inspect_err(|err| {
152            error!(?err, filter = ?filt_in, "internal exists failure");
153        })?;
154
155        if b {
158            Ok(false)
159        } else {
160            Ok(true)
162        }
163    }
164
165    #[instrument(level = "debug", skip_all)]
166    fn check_refers_to_target_loop_slow(
167        qs: &mut QueryServerWriteTransaction,
168        inner: &[Uuid],
169    ) -> Result<Vec<Uuid>, OperationError> {
170        if inner.is_empty() {
171            trace!("no refers types modified, skipping check");
174            return Ok(Vec::with_capacity(0));
175        }
176
177        let mut looping = Vec::with_capacity(inner.len());
178        for u in inner {
179            let filt_in = filter!(f_and(vec![
180                f_eq(Attribute::Uuid, PartialValue::Uuid(*u)),
181                f_pres(Attribute::Refers)
182            ]));
183            let b = qs.internal_exists(&filt_in).inspect_err(|err| {
184                error!(?err, filter = ?filt_in, "internal exists failure");
185            })?;
186
187            if !b {
189                looping.push(*u)
190            }
191        }
192
193        Ok(looping)
194    }
195}
196
197impl Plugin for ReferentialIntegrity {
198    fn id() -> &'static str {
199        "referential_integrity"
200    }
201
202    #[instrument(level = "debug", name = "refint_post_create", skip(qs, cand, _ce))]
217    fn post_create(
218        qs: &mut QueryServerWriteTransaction,
219        cand: &[Entry<EntrySealed, EntryCommitted>],
220        _ce: &CreateEvent,
221    ) -> Result<(), OperationError> {
222        Self::post_modify_inner(qs, None, cand)
223    }
224
225    #[instrument(level = "debug", name = "refint_post_modify", skip_all)]
226    fn post_modify(
227        qs: &mut QueryServerWriteTransaction,
228        pre_cand: &[Arc<EntrySealedCommitted>],
229        cand: &[Entry<EntrySealed, EntryCommitted>],
230        _me: &ModifyEvent,
231    ) -> Result<(), OperationError> {
232        Self::post_modify_inner(qs, Some(pre_cand), cand)
233    }
234
235    #[instrument(level = "debug", name = "refint_post_batch_modify", skip_all)]
236    fn post_batch_modify(
237        qs: &mut QueryServerWriteTransaction,
238        pre_cand: &[Arc<EntrySealedCommitted>],
239        cand: &[Entry<EntrySealed, EntryCommitted>],
240        _me: &BatchModifyEvent,
241    ) -> Result<(), OperationError> {
242        Self::post_modify_inner(qs, Some(pre_cand), cand)
243    }
244
245    #[instrument(level = "debug", name = "refint_post_repl_refresh", skip_all)]
246    fn post_repl_refresh(
247        qs: &mut QueryServerWriteTransaction,
248        cand: &[EntrySealedCommitted],
249    ) -> Result<(), OperationError> {
250        Self::post_modify_inner(qs, None, cand)
251    }
252
253    #[instrument(level = "debug", name = "attrunique_post_repl_incremental", skip_all)]
254    fn post_repl_incremental_conflict(
255        qs: &mut QueryServerWriteTransaction,
256        _cand: &[(EntrySealedCommitted, Arc<EntrySealedCommitted>)],
257        conflict_uuids: &mut BTreeSet<Uuid>,
258    ) -> Result<(), OperationError> {
259        if conflict_uuids.is_empty() {
263            return Ok(());
264        }
265
266        let filt_inner = conflict_uuids
268            .iter()
269            .copied()
270            .map(|conflict_uuid| f_eq(Attribute::Refers, PartialValue::Refer(conflict_uuid)))
271            .collect::<Vec<_>>();
272
273        let filt = filter!(f_or(filt_inner));
274
275        let conflict_cand = qs.internal_exists(&filt).inspect_err(|err| {
277            error!(?err, "internal exists error");
278        })?;
279
280        if conflict_cand {
282            let mut work_set = qs.internal_search_writeable(&filt)?;
283
284            for (_, entry) in work_set.iter_mut() {
285                let Some(uuid) = entry.get_uuid() else {
286                    error!("Impossible state. Entry that was declared in conflict map does not have a uuid.");
287                    return Err(OperationError::InvalidState);
288                };
289
290                conflict_uuids.insert(uuid);
292
293                entry.to_conflict([uuid]);
295            }
296
297            qs.internal_apply_writable(work_set).map_err(|e| {
298                admin_error!("Failed to commit memberof group set {:?}", e);
299                e
300            })?;
301        }
302
303        Ok(())
305    }
306
307    #[instrument(level = "debug", name = "refint_post_repl_incremental", skip_all)]
308    fn post_repl_incremental(
309        qs: &mut QueryServerWriteTransaction,
310        pre_cand: &[Arc<EntrySealedCommitted>],
311        cand: &[EntrySealedCommitted],
312        conflict_uuids: &BTreeSet<Uuid>,
313    ) -> Result<(), OperationError> {
314        let uuids = Self::cand_references_to_uuid_filter(qs, Some(pre_cand), cand)?;
322
323        let all_exist_fast = Self::check_uuids_exist_fast(qs, uuids.as_slice())?;
324
325        let mut missing_uuids = if !all_exist_fast {
326            debug!("Not all uuids referenced by these candidates exist. Slow path to remove them.");
327            Self::check_uuids_exist_slow(qs, uuids.as_slice())?
328        } else {
329            debug!("All references are valid!");
330            Vec::with_capacity(0)
331        };
332
333        let inactive_entries: Vec<_> = std::iter::zip(pre_cand, cand)
337            .filter_map(|(pre, post)| {
338                let pre_live = pre.mask_recycled_ts().is_some();
339                let post_live = post.mask_recycled_ts().is_some();
340
341                if !post_live && (pre_live != post_live) {
342                    Some(post.get_uuid())
345                } else {
346                    None
347                }
348            })
349            .collect();
350
351        if event_enabled!(tracing::Level::DEBUG) {
352            debug!("Removing the following reference uuids for entries that have become recycled or tombstoned");
353            for missing in &inactive_entries {
354                debug!(?missing);
355            }
356        }
357
358        if !conflict_uuids.is_empty() {
369            warn!("conflict uuids have been found, and must be cleaned from existing references. This is to prevent group memberships leaking to un-intended recipients.");
370        }
371
372        missing_uuids.extend(conflict_uuids.iter().copied());
375        missing_uuids.extend_from_slice(&inactive_entries);
376
377        if missing_uuids.is_empty() {
378            trace!("Nothing to do, shortcut");
379            return Ok(());
380        }
381
382        if event_enabled!(tracing::Level::DEBUG) {
383            debug!("Removing the following missing reference uuids");
384            for missing in &missing_uuids {
385                debug!(?missing);
386            }
387        }
388
389        Self::remove_references(qs, missing_uuids)
392
393        }
395
396    #[instrument(level = "debug", name = "refint_post_delete", skip_all)]
397    fn post_delete(
398        qs: &mut QueryServerWriteTransaction,
399        cand: &[Entry<EntrySealed, EntryCommitted>],
400        _ce: &DeleteEvent,
401    ) -> Result<(), OperationError> {
402        let uuids: Vec<Uuid> = cand.iter().map(|e| e.get_uuid()).collect();
408
409        Self::remove_references(qs, uuids)
410    }
411
412    #[instrument(level = "debug", name = "refint::verify", skip_all)]
413    fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
414        let filt_in = filter_all!(f_pres(Attribute::Class));
417
418        let all_cand = match qs
419            .internal_search(filt_in)
420            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
421        {
422            Ok(all_cand) => all_cand,
423            Err(e) => return vec![e],
424        };
425
426        let acu_map: HashSet<Uuid> = all_cand.iter().map(|e| e.get_uuid()).collect();
427
428        let schema = qs.get_schema();
429        let ref_types = schema.get_reference_types();
430
431        let mut res = Vec::with_capacity(0);
432        for c in &all_cand {
434            for rtype in ref_types.values() {
436                if let Some(vs) = c.get_ava_set(&rtype.name) {
438                    match vs.as_ref_uuid_iter() {
440                        Some(uuid_iter) => {
441                            for vu in uuid_iter {
442                                if acu_map.get(&vu).is_none() {
443                                    res.push(Err(ConsistencyError::RefintNotUpheld(c.get_id())))
444                                }
445                            }
446                        }
447                        None => res.push(Err(ConsistencyError::InvalidAttributeType(
448                            "A non-value-ref type was found.".to_string(),
449                        ))),
450                    }
451                }
452            }
453        }
454
455        res
456    }
457}
458
459fn update_reference_set<'a, I>(
460    ref_types: &HashMap<Attribute, SchemaAttribute>,
461    entry_iter: I,
462    reference_set: &mut BTreeSet<Uuid>,
463) -> Result<(), OperationError>
464where
465    I: Iterator<Item = &'a EntrySealedCommitted>,
466{
467    for cand in entry_iter {
468        trace!(cand_id = %cand.get_display_id());
469        let dyn_group = cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into());
471
472        let cand_ref_valuesets = ref_types.values().filter_map(|rtype| {
474            let skip_mb = dyn_group && rtype.name == Attribute::DynMember;
476            let skip_mo = rtype.name == Attribute::MemberOf;
478
479            if skip_mb || skip_mo {
480                None
481            } else {
482                cand.get_ava_set(&rtype.name)
483            }
484        });
485
486        for vs in cand_ref_valuesets {
487            if let Some(uuid_iter) = vs.as_ref_uuid_iter() {
488                reference_set.extend(uuid_iter);
489                Ok(())
490            } else {
491                error!(?vs, "reference value could not convert to reference uuid.");
492                error!("If you are sure the name/uuid/spn exist, and that this is in error, you should run a verify task.");
493                Err(OperationError::InvalidAttribute(
494                    "uuid could not become reference value".to_string(),
495                ))
496            }?;
497        }
498    }
499    Ok(())
500}
501
502impl ReferentialIntegrity {
503    fn cand_references_to_uuid_filter(
504        qs: &mut QueryServerWriteTransaction,
505        pre_cand: Option<&[Arc<EntrySealedCommitted>]>,
506        post_cand: &[EntrySealedCommitted],
507    ) -> Result<Vec<Uuid>, OperationError> {
508        let schema = qs.get_schema();
509        let ref_types = schema.get_reference_types();
510
511        let mut previous_reference_set = BTreeSet::new();
512        let mut reference_set = BTreeSet::new();
513
514        if let Some(pre_cand) = pre_cand {
515            update_reference_set(
516                ref_types,
517                pre_cand.iter().map(|e| e.as_ref()),
518                &mut previous_reference_set,
519            )?;
520        }
521
522        update_reference_set(ref_types, post_cand.iter(), &mut reference_set)?;
523
524        Ok(reference_set
529            .difference(&previous_reference_set)
530            .copied()
531            .collect())
532    }
533
534    fn cand_refers_to_target_uuid(post_cand: &[EntrySealedCommitted]) -> Vec<Uuid> {
535        post_cand
536            .iter()
537            .filter_map(|entry| entry.get_ava_single_refer(Attribute::Refers))
538            .collect()
539    }
540
541    fn post_modify_inner(
542        qs: &mut QueryServerWriteTransaction,
543        pre_cand: Option<&[Arc<EntrySealedCommitted>]>,
544        post_cand: &[EntrySealedCommitted],
545    ) -> Result<(), OperationError> {
546        let uuids = Self::cand_references_to_uuid_filter(qs, pre_cand, post_cand)?;
547
548        let all_exist_fast = Self::check_uuids_exist_fast(qs, uuids.as_slice())?;
549
550        if !all_exist_fast {
551            let missing_uuids = Self::check_uuids_exist_slow(qs, uuids.as_slice())?;
553
554            error!("some uuids that were referenced in this operation do not exist.");
555            for missing in missing_uuids {
556                error!(?missing);
557            }
558
559            return Err(OperationError::Plugin(PluginError::ReferentialIntegrity(
560                "Uuid referenced not found in database".to_string(),
561            )));
562        }
563
564        let refers_target_uuids = Self::cand_refers_to_target_uuid(post_cand);
568
569        let loops_present =
572            Self::check_refers_to_target_loop_fast(qs, refers_target_uuids.as_slice())?;
573
574        if loops_present {
575            let looping_uuids =
576                Self::check_refers_to_target_loop_slow(qs, refers_target_uuids.as_slice())?;
577
578            error!("some entries introduce an invalid reference to another reference.");
579            error!(?looping_uuids);
580
581            return Err(OperationError::ReferenceLoop);
582        }
583
584        Ok(())
586    }
587}
588
589#[cfg(test)]
590mod tests {
591    use crate::credential::Credential;
592    use crate::event::CreateEvent;
593    use crate::prelude::*;
594    use crate::value::{AuthType, Oauth2Session, OauthClaimMapJoin, Session, SessionState};
595    use kanidm_lib_crypto::x509_cert::{der::DecodePem, Certificate};
596    use kanidm_lib_crypto::CryptoPolicy;
597    use kanidm_proto::internal::Filter as ProtoFilter;
598    use time::OffsetDateTime;
599
600    const TEST_TESTGROUP_A_UUID: &str = "d2b496bd-8493-47b7-8142-f568b5cf47ee";
601    const TEST_TESTGROUP_B_UUID: &str = "8cef42bc-2cac-43e4-96b3-8f54561885ca";
602
603    #[test]
605    fn test_create_uuid_reference_not_exist() {
606        let e = entry_init!(
607            (Attribute::Class, EntryClass::Group.to_value()),
608            (Attribute::Name, Value::new_iname("testgroup")),
609            (Attribute::Description, Value::new_utf8s("testgroup")),
610            (
611                Attribute::Member,
612                Value::Refer(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap())
613            )
614        );
615
616        let create = vec![e];
617        let preload = Vec::with_capacity(0);
618        run_create_test!(
619            Err(OperationError::Plugin(PluginError::ReferentialIntegrity(
620                "Uuid referenced not found in database".to_string()
621            ))),
622            preload,
623            create,
624            None,
625            |_| {}
626        );
627    }
628
629    #[test]
631    fn test_create_uuid_reference_exist() {
632        let ea = entry_init!(
633            (Attribute::Class, EntryClass::Group.to_value()),
634            (Attribute::Name, Value::new_iname("testgroup_a")),
635            (Attribute::Description, Value::new_utf8s("testgroup")),
636            (
637                Attribute::Uuid,
638                Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
639            )
640        );
641        let eb = entry_init!(
642            (Attribute::Class, EntryClass::Group.to_value()),
643            (Attribute::Name, Value::new_iname("testgroup_b")),
644            (Attribute::Description, Value::new_utf8s("testgroup")),
645            (
646                Attribute::Member,
647                Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
648            )
649        );
650
651        let preload = vec![ea];
652        let create = vec![eb];
653
654        run_create_test!(
655            Ok(None),
656            preload,
657            create,
658            None,
659            |qs: &mut QueryServerWriteTransaction| {
660                let cands = qs
661                    .internal_search(filter!(f_eq(
662                        Attribute::Name,
663                        PartialValue::new_iname("testgroup_b")
664                    )))
665                    .expect("Internal search failure");
666                let _ue = cands.first().expect("No cand");
667            }
668        );
669    }
670
671    #[test]
673    fn test_create_uuid_reference_self() {
674        let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
675
676        let id = uuid::uuid!("8cef42bc-2cac-43e4-96b3-8f54561885ca");
677
678        let e_group = entry_init!(
679            (Attribute::Class, EntryClass::Group.to_value()),
680            (Attribute::Name, Value::new_iname("testgroup")),
681            (Attribute::Uuid, Value::Uuid(id)),
682            (Attribute::Member, Value::Refer(id))
683        );
684
685        let create = vec![e_group];
686
687        run_create_test!(
688            Ok(None),
689            preload,
690            create,
691            None,
692            |qs: &mut QueryServerWriteTransaction| {
693                let cands = qs
694                    .internal_search(filter!(f_eq(
695                        Attribute::Name,
696                        PartialValue::new_iname("testgroup")
697                    )))
698                    .expect("Internal search failure");
699                let _ue = cands.first().expect("No cand");
700            }
701        );
702    }
703
704    #[test]
706    fn test_modify_uuid_reference_exist() {
707        let ea = entry_init!(
708            (Attribute::Class, EntryClass::Group.to_value()),
709            (Attribute::Name, Value::new_iname("testgroup_a")),
710            (
711                Attribute::Uuid,
712                Value::Uuid(uuid::uuid!(TEST_TESTGROUP_A_UUID))
713            )
714        );
715
716        let eb = entry_init!(
717            (Attribute::Class, EntryClass::Group.to_value()),
718            (Attribute::Name, Value::new_iname("testgroup_b"))
719        );
720
721        let preload = vec![ea, eb];
722
723        run_modify_test!(
724            Ok(()),
725            preload,
726            filter!(f_eq(
727                Attribute::Name,
728                PartialValue::new_iname("testgroup_b")
729            )),
730            ModifyList::new_list(vec![Modify::Present(
731                Attribute::Member,
732                Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
733            )]),
734            None,
735            |_| {},
736            |_| {}
737        );
738    }
739
740    #[test]
742    fn test_modify_uuid_reference_not_exist() {
743        let eb = entry_init!(
744            (Attribute::Class, EntryClass::Group.to_value()),
745            (Attribute::Name, Value::new_iname("testgroup_b"))
746        );
747
748        let preload = vec![eb];
749
750        run_modify_test!(
751            Err(OperationError::Plugin(PluginError::ReferentialIntegrity(
752                "Uuid referenced not found in database".to_string()
753            ))),
754            preload,
755            filter!(f_eq(
756                Attribute::Name,
757                PartialValue::new_iname("testgroup_b")
758            )),
759            ModifyList::new_list(vec![Modify::Present(
760                Attribute::Member,
761                Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
762            )]),
763            None,
764            |_| {},
765            |_| {}
766        );
767    }
768
769    #[test]
772    fn test_modify_uuid_reference_partial_not_exist() {
773        let ea = entry_init!(
774            (Attribute::Class, EntryClass::Group.to_value()),
775            (Attribute::Name, Value::new_iname("testgroup_a")),
776            (
777                Attribute::Uuid,
778                Value::Uuid(uuid::uuid!(TEST_TESTGROUP_A_UUID))
779            )
780        );
781
782        let eb = entry_init!(
783            (Attribute::Class, EntryClass::Group.to_value()),
784            (Attribute::Name, Value::new_iname("testgroup_b"))
785        );
786
787        let preload = vec![ea, eb];
788
789        run_modify_test!(
790            Err(OperationError::Plugin(PluginError::ReferentialIntegrity(
791                "Uuid referenced not found in database".to_string()
792            ))),
793            preload,
794            filter!(f_eq(
795                Attribute::Name,
796                PartialValue::new_iname("testgroup_b")
797            )),
798            ModifyList::new_list(vec![
799                Modify::Present(
800                    Attribute::Member,
801                    Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
802                ),
803                Modify::Present(Attribute::Member, Value::Refer(UUID_DOES_NOT_EXIST)),
804            ]),
805            None,
806            |_| {},
807            |_| {}
808        );
809    }
810
811    #[test]
813    fn test_modify_remove_referee() {
814        let ea = entry_init!(
815            (Attribute::Class, EntryClass::Group.to_value()),
816            (Attribute::Name, Value::new_iname("testgroup_a")),
817            (
818                Attribute::Uuid,
819                Value::Uuid(uuid::uuid!(TEST_TESTGROUP_A_UUID))
820            )
821        );
822
823        let eb = entry_init!(
824            (Attribute::Class, EntryClass::Group.to_value()),
825            (Attribute::Name, Value::new_iname("testgroup_b")),
826            (
827                Attribute::Member,
828                Value::Refer(uuid::uuid!(TEST_TESTGROUP_A_UUID))
829            )
830        );
831
832        let preload = vec![ea, eb];
833
834        run_modify_test!(
835            Ok(()),
836            preload,
837            filter!(f_eq(
838                Attribute::Name,
839                PartialValue::new_iname("testgroup_b")
840            )),
841            ModifyList::new_list(vec![Modify::Purged(Attribute::Member)]),
842            None,
843            |_| {},
844            |_| {}
845        );
846    }
847
848    #[test]
850    fn test_modify_uuid_reference_self() {
851        let ea = entry_init!(
852            (Attribute::Class, EntryClass::Group.to_value()),
853            (Attribute::Name, Value::new_iname("testgroup_a")),
854            (
855                Attribute::Uuid,
856                Value::Uuid(uuid::uuid!(TEST_TESTGROUP_A_UUID))
857            )
858        );
859
860        let preload = vec![ea];
861
862        run_modify_test!(
863            Ok(()),
864            preload,
865            filter!(f_eq(
866                Attribute::Name,
867                PartialValue::new_iname("testgroup_a")
868            )),
869            ModifyList::new_list(vec![Modify::Present(
870                Attribute::Member,
871                Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
872            )]),
873            None,
874            |_| {},
875            |_| {}
876        );
877    }
878
879    #[test]
881    fn test_modify_reference_deleted() {
882        let ea = entry_init!(
883            (Attribute::Class, EntryClass::Group.to_value()),
884            (Attribute::Name, Value::new_iname("testgroup_a")),
885            (
886                Attribute::Uuid,
887                Value::Uuid(uuid::uuid!(TEST_TESTGROUP_A_UUID))
888            )
889        );
890
891        let eb = entry_init!(
892            (Attribute::Class, EntryClass::Group.to_value()),
893            (Attribute::Name, Value::new_iname("testgroup_b"))
894        );
895
896        let preload = vec![ea, eb];
897
898        run_modify_test!(
899            Err(OperationError::Plugin(PluginError::ReferentialIntegrity(
900                "Uuid referenced not found in database".to_string()
901            ))),
902            preload,
903            filter!(f_eq(
904                Attribute::Name,
905                PartialValue::new_iname("testgroup_b")
906            )),
907            ModifyList::new_list(vec![Modify::Present(
908                Attribute::Member,
909                Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
910            )]),
911            None,
912            |qs: &mut QueryServerWriteTransaction| {
913                let de_sin =
915                    crate::event::DeleteEvent::new_internal_invalid(filter!(f_or!([f_eq(
916                        Attribute::Name,
917                        PartialValue::new_iname("testgroup_a")
918                    )])));
919                assert!(qs.delete(&de_sin).is_ok());
920            },
921            |_| {}
922        );
923    }
924
925    #[test]
929    fn test_delete_remove_referent_valid() {
930        let ea: Entry<EntryInit, EntryNew> = entry_init!(
931            (Attribute::Class, EntryClass::Group.to_value()),
932            (Attribute::Name, Value::new_iname("testgroup_a")),
933            (Attribute::Description, Value::new_utf8s("testgroup")),
934            (
935                Attribute::Uuid,
936                Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
937            )
938        );
939        let eb: Entry<EntryInit, EntryNew> = entry_init!(
940            (Attribute::Class, EntryClass::Group.to_value()),
941            (Attribute::Name, Value::new_iname("testgroup_b")),
942            (Attribute::Description, Value::new_utf8s("testgroup")),
943            (
944                Attribute::Member,
945                Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
946            )
947        );
948
949        let preload = vec![ea, eb];
950
951        run_delete_test!(
952            Ok(()),
953            preload,
954            filter!(f_eq(
955                Attribute::Name,
956                PartialValue::new_iname("testgroup_a")
957            )),
958            None,
959            |_qs: &mut QueryServerWriteTransaction| {}
960        );
961    }
962
963    #[test]
974    fn test_delete_remove_referent_invalid() {
975        let target_uuid = Uuid::new_v4();
976
977        let e_group: Entry<EntryInit, EntryNew> = entry_init!(
978            (Attribute::Class, EntryClass::Group.to_value()),
979            (Attribute::Name, Value::new_iname("testgroup_a")),
980            (Attribute::Description, Value::new_utf8s("testgroup")),
981            (Attribute::Uuid, Value::Uuid(target_uuid))
982        );
983
984        let e_acp: Entry<EntryInit, EntryNew> = entry_init!(
985            (Attribute::Class, EntryClass::Object.to_value()),
986            (
987                Attribute::Class,
988                EntryClass::AccessControlProfile.to_value()
989            ),
990            (
991                Attribute::Class,
992                EntryClass::AccessControlReceiverGroup.to_value()
993            ),
994            (
995                Attribute::Class,
996                EntryClass::AccessControlTargetScope.to_value()
997            ),
998            (Attribute::Name, Value::new_iname("acp_referer")),
999            (Attribute::AcpReceiverGroup, Value::Refer(target_uuid)),
1000            (
1001                Attribute::AcpTargetScope,
1002                Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1003            )
1004        );
1005
1006        let preload = vec![e_group, e_acp];
1007
1008        run_delete_test!(
1009            Err(OperationError::SchemaViolation(
1010                SchemaError::MissingMustAttribute(vec![Attribute::AcpReceiverGroup])
1011            )),
1012            preload,
1013            filter!(f_eq(
1014                Attribute::Name,
1015                PartialValue::new_iname("testgroup_a")
1016            )),
1017            None,
1018            |_qs: &mut QueryServerWriteTransaction| {}
1019        );
1020    }
1021
1022    #[test]
1024    fn test_delete_remove_referee() {
1025        let ea: Entry<EntryInit, EntryNew> = entry_init!(
1026            (Attribute::Class, EntryClass::Group.to_value()),
1027            (Attribute::Name, Value::new_iname("testgroup_a")),
1028            (Attribute::Description, Value::new_utf8s("testgroup")),
1029            (
1030                Attribute::Uuid,
1031                Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
1032            )
1033        );
1034        let eb: Entry<EntryInit, EntryNew> = entry_init!(
1035            (Attribute::Class, EntryClass::Group.to_value()),
1036            (Attribute::Name, Value::new_iname("testgroup_b")),
1037            (Attribute::Description, Value::new_utf8s("testgroup")),
1038            (
1039                Attribute::Member,
1040                Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
1041            )
1042        );
1043
1044        let preload = vec![ea, eb];
1045
1046        run_delete_test!(
1047            Ok(()),
1048            preload,
1049            filter!(f_eq(
1050                Attribute::Name,
1051                PartialValue::new_iname("testgroup_b")
1052            )),
1053            None,
1054            |_qs: &mut QueryServerWriteTransaction| {}
1055        );
1056    }
1057
1058    #[test]
1060    fn test_delete_remove_reference_self() {
1061        let eb: Entry<EntryInit, EntryNew> = entry_init!(
1062            (Attribute::Class, EntryClass::Group.to_value()),
1063            (Attribute::Name, Value::new_iname("testgroup_b")),
1064            (Attribute::Description, Value::new_utf8s("testgroup")),
1065            (
1066                Attribute::Uuid,
1067                Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
1068            ),
1069            (
1070                Attribute::Member,
1071                Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
1072            )
1073        );
1074        let preload = vec![eb];
1075
1076        run_delete_test!(
1077            Ok(()),
1078            preload,
1079            filter!(f_eq(
1080                Attribute::Name,
1081                PartialValue::new_iname("testgroup_b")
1082            )),
1083            None,
1084            |_qs: &mut QueryServerWriteTransaction| {}
1085        );
1086    }
1087
1088    #[test]
1089    fn test_delete_remove_reference_oauth2() {
1090        let ea: Entry<EntryInit, EntryNew> = entry_init!(
1094            (Attribute::Class, EntryClass::Object.to_value()),
1095            (Attribute::Class, EntryClass::Account.to_value()),
1096            (
1097                Attribute::Class,
1098                EntryClass::OAuth2ResourceServer.to_value()
1099            ),
1100            (Attribute::Name, Value::new_iname("test_resource_server")),
1102            (
1103                Attribute::DisplayName,
1104                Value::new_utf8s("test_resource_server")
1105            ),
1106            (
1107                Attribute::OAuth2RsOriginLanding,
1108                Value::new_url_s("https://demo.example.com").unwrap()
1109            ),
1110            (
1111                Attribute::OAuth2RsScopeMap,
1112                Value::new_oauthscopemap(
1113                    Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap(),
1114                    btreeset![OAUTH2_SCOPE_READ.to_string()]
1115                )
1116                .expect("Invalid scope")
1117            )
1118        );
1119
1120        let eb: Entry<EntryInit, EntryNew> = entry_init!(
1121            (Attribute::Class, EntryClass::Group.to_value()),
1122            (Attribute::Name, Value::new_iname("testgroup")),
1123            (
1124                Attribute::Uuid,
1125                Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap())
1126            ),
1127            (Attribute::Description, Value::new_utf8s("testgroup"))
1128        );
1129
1130        let preload = vec![ea, eb];
1131
1132        run_delete_test!(
1133            Ok(()),
1134            preload,
1135            filter!(f_eq(Attribute::Name, PartialValue::new_iname("testgroup"))),
1136            None,
1137            |qs: &mut QueryServerWriteTransaction| {
1138                let cands = qs
1139                    .internal_search(filter!(f_eq(
1140                        Attribute::Name,
1141                        PartialValue::new_iname("test_resource_server")
1142                    )))
1143                    .expect("Internal search failure");
1144                let ue = cands.first().expect("No entry");
1145                assert!(ue
1146                    .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
1147                    .is_none())
1148            }
1149        );
1150    }
1151
1152    #[qs_test]
1153    async fn test_delete_oauth2_rs_remove_sessions(server: &QueryServer) {
1154        let curtime = duration_from_epoch_now();
1155        let curtime_odt = OffsetDateTime::UNIX_EPOCH + curtime;
1156
1157        let p = CryptoPolicy::minimum();
1158        let cred = Credential::new_password_only(&p, "test_password").unwrap();
1159        let cred_id = cred.uuid;
1160
1161        let mut server_txn = server.write(curtime).await.unwrap();
1163
1164        let tuuid = Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap();
1165        let rs_uuid = Uuid::new_v4();
1166
1167        let e1 = entry_init!(
1168            (Attribute::Class, EntryClass::Object.to_value()),
1169            (Attribute::Class, EntryClass::Person.to_value()),
1170            (Attribute::Class, EntryClass::Account.to_value()),
1171            (Attribute::Name, Value::new_iname("testperson1")),
1172            (Attribute::Uuid, Value::Uuid(tuuid)),
1173            (Attribute::Description, Value::new_utf8s("testperson1")),
1174            (Attribute::DisplayName, Value::new_utf8s("testperson1")),
1175            (
1176                Attribute::PrimaryCredential,
1177                Value::Cred("primary".to_string(), cred.clone())
1178            )
1179        );
1180
1181        let e2 = entry_init!(
1182            (Attribute::Class, EntryClass::Object.to_value()),
1183            (Attribute::Class, EntryClass::Account.to_value()),
1184            (
1185                Attribute::Class,
1186                EntryClass::OAuth2ResourceServer.to_value()
1187            ),
1188            (Attribute::Uuid, Value::Uuid(rs_uuid)),
1189            (Attribute::Name, Value::new_iname("test_resource_server")),
1190            (
1191                Attribute::DisplayName,
1192                Value::new_utf8s("test_resource_server")
1193            ),
1194            (
1195                Attribute::OAuth2RsOriginLanding,
1196                Value::new_url_s("https://demo.example.com").unwrap()
1197            ),
1198            (
1200                Attribute::OAuth2RsScopeMap,
1201                Value::new_oauthscopemap(
1202                    UUID_IDM_ALL_ACCOUNTS,
1203                    btreeset![OAUTH2_SCOPE_OPENID.to_string()]
1204                )
1205                .expect("invalid oauthscope")
1206            )
1207        );
1208
1209        let ce = CreateEvent::new_internal(vec![e1, e2]);
1210        assert!(server_txn.create(&ce).is_ok());
1211
1212        let session_id = Uuid::new_v4();
1215        let pv_session_id = PartialValue::Refer(session_id);
1216
1217        let parent_id = Uuid::new_v4();
1218        let pv_parent_id = PartialValue::Refer(parent_id);
1219        let issued_at = curtime_odt;
1220        let issued_by = IdentityId::User(tuuid);
1221        let scope = SessionScope::ReadOnly;
1222
1223        let modlist = modlist!([
1225            Modify::Present(
1226                Attribute::OAuth2Session,
1227                Value::Oauth2Session(
1228                    session_id,
1229                    Oauth2Session {
1230                        parent: Some(parent_id),
1231                        state: SessionState::NeverExpires,
1233                        issued_at,
1234                        rs_uuid,
1235                    },
1236                )
1237            ),
1238            Modify::Present(
1239                Attribute::UserAuthTokenSession,
1240                Value::Session(
1241                    parent_id,
1242                    Session {
1243                        label: "label".to_string(),
1244                        state: SessionState::NeverExpires,
1246                        issued_at,
1249                        issued_by,
1251                        cred_id,
1252                        scope,
1255                        type_: AuthType::Passkey,
1256                        ext_metadata: Default::default(),
1257                    },
1258                )
1259            ),
1260        ]);
1261
1262        server_txn
1263            .internal_modify(
1264                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(tuuid))),
1265                &modlist,
1266            )
1267            .expect("Failed to modify user");
1268
1269        let entry = server_txn.internal_search_uuid(tuuid).expect("failed");
1272        assert!(entry.attribute_equality(Attribute::UserAuthTokenSession, &pv_parent_id));
1273        assert!(entry.attribute_equality(Attribute::OAuth2Session, &pv_session_id));
1274
1275        assert!(server_txn.internal_delete_uuid(rs_uuid).is_ok());
1277
1278        let entry = server_txn.internal_search_uuid(tuuid).expect("failed");
1280
1281        let session = entry
1283            .get_ava_as_session_map(Attribute::UserAuthTokenSession)
1284            .and_then(|sessions| sessions.get(&parent_id))
1285            .expect("No session map found");
1286        assert!(matches!(session.state, SessionState::NeverExpires));
1287
1288        let session = entry
1290            .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
1291            .and_then(|sessions| sessions.get(&session_id))
1292            .expect("No session map found");
1293        assert!(matches!(session.state, SessionState::RevokedAt(_)));
1294
1295        assert!(server_txn.commit().is_ok());
1296    }
1297
1298    #[qs_test]
1299    async fn test_ignore_references_for_regen(server: &QueryServer) {
1300        let curtime = duration_from_epoch_now();
1306        let mut server_txn = server.write(curtime).await.unwrap();
1307
1308        let tgroup_uuid = Uuid::new_v4();
1309        let dyn_uuid = Uuid::new_v4();
1310        let inv_mo_uuid = Uuid::new_v4();
1311        let inv_mb_uuid = Uuid::new_v4();
1312
1313        let e_dyn = entry_init!(
1314            (Attribute::Class, EntryClass::Object.to_value()),
1315            (Attribute::Class, EntryClass::Group.to_value()),
1316            (Attribute::Class, EntryClass::DynGroup.to_value()),
1317            (Attribute::Uuid, Value::Uuid(dyn_uuid)),
1318            (Attribute::Name, Value::new_iname("test_dyngroup")),
1319            (Attribute::DynMember, Value::Refer(inv_mb_uuid)),
1320            (
1321                Attribute::DynGroupFilter,
1322                Value::JsonFilt(ProtoFilter::Eq(
1323                    Attribute::Name.to_string(),
1324                    "testgroup".to_string()
1325                ))
1326            )
1327        );
1328
1329        let e_group: Entry<EntryInit, EntryNew> = entry_init!(
1330            (Attribute::Class, EntryClass::Group.to_value()),
1331            (Attribute::Class, EntryClass::MemberOf.to_value()),
1332            (Attribute::Name, Value::new_iname("testgroup")),
1333            (Attribute::Uuid, Value::Uuid(tgroup_uuid)),
1334            (Attribute::MemberOf, Value::Refer(inv_mo_uuid))
1335        );
1336
1337        let ce = CreateEvent::new_internal(vec![e_dyn, e_group]);
1338        assert!(server_txn.create(&ce).is_ok());
1339
1340        let dyna = server_txn
1341            .internal_search_uuid(dyn_uuid)
1342            .expect("Failed to access dyn group");
1343
1344        let dyn_member = dyna
1345            .get_ava_refer(Attribute::DynMember)
1346            .expect("Failed to get dyn member attribute");
1347        assert_eq!(dyn_member.len(), 1);
1348        assert!(dyn_member.contains(&tgroup_uuid));
1349
1350        let group = server_txn
1351            .internal_search_uuid(tgroup_uuid)
1352            .expect("Failed to access mo group");
1353
1354        let grp_member = group
1355            .get_ava_refer(Attribute::MemberOf)
1356            .expect("Failed to get memberof attribute");
1357        assert_eq!(grp_member.len(), 1);
1358        assert!(grp_member.contains(&dyn_uuid));
1359
1360        assert!(server_txn.commit().is_ok());
1361    }
1362
1363    #[qs_test]
1364    async fn test_entry_managed_by_references(server: &QueryServer) {
1365        let curtime = duration_from_epoch_now();
1366        let mut server_txn = server.write(curtime).await.unwrap();
1367
1368        let manages_uuid = Uuid::new_v4();
1369        let e_manages: Entry<EntryInit, EntryNew> = entry_init!(
1370            (Attribute::Class, EntryClass::Group.to_value()),
1371            (Attribute::Name, Value::new_iname("entry_manages")),
1372            (Attribute::Uuid, Value::Uuid(manages_uuid))
1373        );
1374
1375        let group_uuid = Uuid::new_v4();
1376        let e_group: Entry<EntryInit, EntryNew> = entry_init!(
1377            (Attribute::Class, EntryClass::Group.to_value()),
1378            (Attribute::Name, Value::new_iname("entry_managed_by")),
1379            (Attribute::Uuid, Value::Uuid(group_uuid)),
1380            (Attribute::EntryManagedBy, Value::Refer(manages_uuid))
1381        );
1382
1383        let ce = CreateEvent::new_internal(vec![e_manages, e_group]);
1384        assert!(server_txn.create(&ce).is_ok());
1385
1386        let group = server_txn
1387            .internal_search_uuid(group_uuid)
1388            .expect("Failed to access group");
1389
1390        let entry_managed_by = group
1391            .get_ava_single_refer(Attribute::EntryManagedBy)
1392            .expect("No entry managed by");
1393
1394        assert_eq!(entry_managed_by, manages_uuid);
1395
1396        assert!(server_txn.internal_delete_uuid(manages_uuid).is_ok());
1398
1399        let group = server_txn
1400            .internal_search_uuid(group_uuid)
1401            .expect("Failed to access group");
1402
1403        assert!(group.get_ava_refer(Attribute::EntryManagedBy).is_none());
1404
1405        assert!(server_txn.commit().is_ok());
1406    }
1407
1408    #[test]
1409    fn test_delete_remove_reference_oauth2_claim_map() {
1410        let ea: Entry<EntryInit, EntryNew> = entry_init!(
1411            (Attribute::Class, EntryClass::Object.to_value()),
1412            (Attribute::Class, EntryClass::Account.to_value()),
1413            (
1414                Attribute::Class,
1415                EntryClass::OAuth2ResourceServer.to_value()
1416            ),
1417            (
1418                Attribute::Class,
1419                EntryClass::OAuth2ResourceServerPublic.to_value()
1420            ),
1421            (Attribute::Name, Value::new_iname("test_resource_server")),
1422            (
1423                Attribute::DisplayName,
1424                Value::new_utf8s("test_resource_server")
1425            ),
1426            (
1427                Attribute::OAuth2RsOriginLanding,
1428                Value::new_url_s("https://demo.example.com").unwrap()
1429            ),
1430            (
1431                Attribute::OAuth2RsClaimMap,
1432                Value::OauthClaimMap(
1433                    "custom_a".to_string(),
1434                    OauthClaimMapJoin::CommaSeparatedValue,
1435                )
1436            ),
1437            (
1438                Attribute::OAuth2RsClaimMap,
1439                Value::OauthClaimValue(
1440                    "custom_a".to_string(),
1441                    Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap(),
1442                    btreeset!["value_a".to_string()],
1443                )
1444            )
1445        );
1446
1447        let eb: Entry<EntryInit, EntryNew> = entry_init!(
1448            (Attribute::Class, EntryClass::Group.to_value()),
1449            (Attribute::Name, Value::new_iname("testgroup")),
1450            (
1451                Attribute::Uuid,
1452                Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap())
1453            ),
1454            (Attribute::Description, Value::new_utf8s("testgroup"))
1455        );
1456
1457        let preload = vec![ea, eb];
1458
1459        run_delete_test!(
1460            Ok(()),
1461            preload,
1462            filter!(f_eq(Attribute::Name, PartialValue::new_iname("testgroup"))),
1463            None,
1464            |qs: &mut QueryServerWriteTransaction| {
1465                let cands = qs
1466                    .internal_search(filter!(f_eq(
1467                        Attribute::Name,
1468                        PartialValue::new_iname("test_resource_server")
1469                    )))
1470                    .expect("Internal search failure");
1471                let ue = cands.first().expect("No entry");
1472
1473                assert!(ue
1474                    .get_ava_set(Attribute::OAuth2RsClaimMap)
1475                    .and_then(|vs| vs.as_oauthclaim_map())
1476                    .is_none())
1477            }
1478        );
1479    }
1480
1481    #[qs_test]
1498    async fn test_reference_entry_basic(server: &QueryServer) {
1499        let curtime = duration_from_epoch_now();
1501        let mut server_txn = server.write(curtime).await.unwrap();
1502
1503        let user_uuid = Uuid::new_v4();
1504        let ref_uuid = Uuid::new_v4();
1505        let cert_data = Box::new(
1506            Certificate::from_pem(TEST_X509_CERT_DATA)
1507                .expect("Unable to parse test X509 cert data"),
1508        );
1509
1510        let user_entry = entry_init!(
1511            (Attribute::Class, EntryClass::Object.to_value()),
1512            (Attribute::Class, EntryClass::Account.to_value()),
1513            (Attribute::Class, EntryClass::Person.to_value()),
1514            (Attribute::Name, Value::new_iname("testperson1")),
1515            (Attribute::Uuid, Value::Uuid(user_uuid)),
1516            (Attribute::Description, Value::new_utf8s("testperson1")),
1517            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1518        );
1519
1520        let ref_entry = entry_init!(
1521            (Attribute::Class, EntryClass::Object.to_value()),
1522            (Attribute::Class, EntryClass::ClientCertificate.to_value()),
1523            (Attribute::Uuid, Value::Uuid(ref_uuid)),
1524            (Attribute::Refers, Value::Refer(user_uuid)),
1525            (
1526                Attribute::Certificate,
1527                Value::Certificate(cert_data.clone())
1528            )
1529        );
1530
1531        assert!(server_txn
1532            .internal_create(vec![user_entry, ref_entry])
1533            .is_ok());
1534
1535        assert!(server_txn.internal_delete_uuid(user_uuid).is_ok());
1537
1538        assert!(!server_txn
1540            .internal_exists_uuid(ref_uuid)
1541            .expect("Unable to check if entry exists"));
1542
1543        assert!(server_txn.commit().is_ok());
1544        let mut server_txn = server.write(curtime).await.unwrap();
1546
1547        assert!(matches!(
1549            server_txn.internal_revive_uuid(ref_uuid).unwrap_err(),
1550            OperationError::Plugin(PluginError::ReferentialIntegrity(_))
1551        ));
1552
1553        drop(server_txn);
1555
1556        let mut server_txn = server.write(curtime).await.unwrap();
1559
1560        assert!(server_txn.internal_revive_uuid(user_uuid).is_ok());
1561
1562        assert!(server_txn
1563            .internal_exists_uuid(user_uuid)
1564            .expect("Unable to check if entry exists"));
1565
1566        assert!(server_txn
1567            .internal_exists_uuid(ref_uuid)
1568            .expect("Unable to check if entry exists"));
1569
1570        assert!(server_txn.commit().is_ok());
1571
1572        let mut server_txn = server.write(curtime).await.unwrap();
1577
1578        assert!(server_txn.internal_delete_uuid(ref_uuid).is_ok());
1579
1580        assert!(server_txn
1581            .internal_exists_uuid(user_uuid)
1582            .expect("Unable to check if entry exists"));
1583
1584        assert!(!server_txn
1585            .internal_exists_uuid(ref_uuid)
1586            .expect("Unable to check if entry exists"));
1587
1588        assert!(server_txn.internal_delete_uuid(user_uuid).is_ok());
1589
1590        assert!(server_txn.commit().is_ok());
1591        let mut server_txn = server.write(curtime).await.unwrap();
1593
1594        assert!(server_txn.internal_revive_uuid(ref_uuid).is_err());
1596
1597        drop(server_txn);
1599        let mut server_txn = server.write(curtime).await.unwrap();
1601        assert!(server_txn.internal_revive_uuid(user_uuid).is_ok());
1602
1603        assert!(server_txn
1604            .internal_exists_uuid(user_uuid)
1605            .expect("Unable to check if entry exists"));
1606
1607        assert!(!server_txn
1609            .internal_exists_uuid(ref_uuid)
1610            .expect("Unable to check if entry exists"));
1611
1612        assert!(server_txn.commit().is_ok());
1613    }
1614
1615    #[qs_test]
1617    async fn test_reference_ouroboros_denied(server: &QueryServer) {
1618        let curtime = duration_from_epoch_now();
1620        let mut server_txn = server.write(curtime).await.unwrap();
1621
1622        let user_uuid = Uuid::new_v4();
1623        let ref_uuid = Uuid::new_v4();
1624        let ref_ouroboros_uuid = Uuid::new_v4();
1625        let cert_data = Box::new(
1626            Certificate::from_pem(TEST_X509_CERT_DATA)
1627                .expect("Unable to parse test X509 cert data"),
1628        );
1629
1630        let user_entry = entry_init!(
1631            (Attribute::Class, EntryClass::Object.to_value()),
1632            (Attribute::Class, EntryClass::Account.to_value()),
1633            (Attribute::Class, EntryClass::Person.to_value()),
1634            (Attribute::Name, Value::new_iname("testperson1")),
1635            (Attribute::Uuid, Value::Uuid(user_uuid)),
1636            (Attribute::Description, Value::new_utf8s("testperson1")),
1637            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1638        );
1639
1640        let ref_entry = entry_init!(
1641            (Attribute::Class, EntryClass::Object.to_value()),
1642            (Attribute::Class, EntryClass::ClientCertificate.to_value()),
1643            (Attribute::Uuid, Value::Uuid(ref_uuid)),
1644            (Attribute::Refers, Value::Refer(user_uuid)),
1645            (
1646                Attribute::Certificate,
1647                Value::Certificate(cert_data.clone())
1648            )
1649        );
1650
1651        assert!(server_txn
1652            .internal_create(vec![user_entry, ref_entry])
1653            .is_ok());
1654
1655        assert!(server_txn.commit().is_ok());
1656
1657        let mut server_txn = server.write(curtime).await.unwrap();
1659
1660        let ref_entry = entry_init!(
1663            (Attribute::Class, EntryClass::Object.to_value()),
1664            (Attribute::Class, EntryClass::ClientCertificate.to_value()),
1665            (Attribute::Uuid, Value::Uuid(ref_ouroboros_uuid)),
1666            (Attribute::Refers, Value::Refer(ref_uuid)),
1667            (
1668                Attribute::Certificate,
1669                Value::Certificate(cert_data.clone())
1670            )
1671        );
1672
1673        assert!(server_txn.internal_create(vec![ref_entry]).is_err());
1674
1675        drop(server_txn);
1677
1678        let mut server_txn = server.write(curtime).await.unwrap();
1680
1681        let ref_entry = entry_init!(
1684            (Attribute::Class, EntryClass::Object.to_value()),
1685            (Attribute::Class, EntryClass::ClientCertificate.to_value()),
1686            (Attribute::Uuid, Value::Uuid(ref_ouroboros_uuid)),
1687            (Attribute::Refers, Value::Refer(user_uuid)),
1689            (
1690                Attribute::Certificate,
1691                Value::Certificate(cert_data.clone())
1692            )
1693        );
1694
1695        assert!(server_txn.internal_create(vec![ref_entry]).is_ok());
1696
1697        let modlist = modlist!([
1699            Modify::Purged(Attribute::Refers),
1700            Modify::Present(Attribute::Refers, Value::Refer(ref_uuid)),
1701        ]);
1702
1703        assert!(server_txn
1704            .internal_modify_uuid(ref_ouroboros_uuid, &modlist)
1705            .is_err());
1706
1707        drop(server_txn);
1709    }
1710
1711    }