1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Arc;
3
4use kanidm_proto::internal::Filter as ProtoFilter;
5
6use crate::filter::FilterInvalid;
7use crate::prelude::*;
8use crate::server::ServerPhase;
9
10#[derive(Clone, Default)]
11pub struct DynGroupCache {
12 insts: BTreeMap<Uuid, Filter<FilterInvalid>>,
13}
14
15pub struct DynGroup;
16
17impl DynGroup {
18 #[allow(clippy::too_many_arguments)]
20 fn apply_dyngroup_change(
21 qs: &mut QueryServerWriteTransaction,
22 affected_uuids: &mut BTreeSet<Uuid>,
25 expect: bool,
28 ident_internal: &Identity,
30 dyn_groups: &mut DynGroupCache,
32 n_dyn_groups: &[&Entry<EntrySealed, EntryCommitted>],
34 ) -> Result<(), OperationError> {
35 if qs.get_phase() < ServerPhase::SchemaReady {
47 debug!("Server is not ready to load dyngroups");
48 return Ok(());
49 }
50
51 let filt = filter!(FC::Or(
53 n_dyn_groups
54 .iter()
55 .map(|e| f_eq(Attribute::Uuid, PartialValue::Uuid(e.get_uuid())))
56 .collect()
57 ));
58 let mut work_set = qs.internal_search_writeable(&filt)?;
60
61 for (ref pre, ref mut nd_group) in work_set.iter_mut() {
63 trace!(dyngroup_id = %nd_group.get_display_id());
64 let scope_f: ProtoFilter = nd_group
66 .get_ava_single_protofilter(Attribute::DynGroupFilter)
67 .cloned()
68 .ok_or_else(|| {
69 error!("Missing {}", Attribute::DynGroupFilter);
70 OperationError::InvalidEntryState
71 })?;
72
73 let scope_i = Filter::from_rw(ident_internal, &scope_f, qs).map_err(|e| {
74 error!("{} validation failed {:?}", Attribute::DynGroupFilter, e);
75 e
76 })?;
77
78 trace!(dyngroup_filter = ?scope_i);
79
80 let uuid = pre.get_uuid();
81 affected_uuids.insert(uuid);
83
84 let entries = qs.internal_search(scope_i.clone()).map_err(|e| {
86 error!("internal search failure -> {:?}", e);
87 e
88 })?;
89
90 trace!(entries_len = %entries.len());
91
92 let members = ValueSetRefer::from_iter(entries.iter().map(|e| e.get_uuid()));
93 trace!(?members);
94
95 if let Some(uuid_iter) = members.as_ref().and_then(|a| a.as_ref_uuid_iter()) {
96 affected_uuids.extend(uuid_iter);
97 }
98
99 if let Some(uuid_iter) = pre.get_ava_as_refuuid(Attribute::DynMember) {
101 affected_uuids.extend(uuid_iter);
102 }
103
104 if let Some(members) = members {
105 nd_group.set_ava_set(&Attribute::DynMember, members);
107 } else {
109 nd_group.purge_ava(Attribute::DynMember);
110 }
111
112 if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
115 error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid);
116 return Err(OperationError::InvalidState);
117 }
118 }
119
120 if !work_set.is_empty() {
121 qs.internal_apply_writable(work_set).map_err(|e| {
122 error!("Failed to commit dyngroup set {:?}", e);
123 e
124 })?;
125 }
126
127 Ok(())
128 }
129
130 #[instrument(level = "debug", name = "dyngroup::reload", skip_all)]
131 pub fn reload(qs: &mut QueryServerWriteTransaction) -> Result<(), OperationError> {
132 let ident_internal = Identity::from_internal();
133 let filt = filter!(f_eq(Attribute::Class, EntryClass::DynGroup.into()));
135 let entries = qs.internal_search(filt).map_err(|e| {
136 error!("internal search failure -> {:?}", e);
137 e
138 })?;
139
140 let mut reload_groups = BTreeMap::default();
141
142 for nd_group in entries.into_iter() {
143 let scope_f: ProtoFilter = nd_group
144 .get_ava_single_protofilter(Attribute::DynGroupFilter)
145 .cloned()
146 .ok_or_else(|| {
147 error!("Missing {}", Attribute::DynGroupFilter);
148 OperationError::InvalidEntryState
149 })?;
150
151 let scope_i = Filter::from_rw(&ident_internal, &scope_f, qs).map_err(|e| {
152 error!("dyngroup_filter validation failed {:?}", e);
153 e
154 })?;
155
156 let uuid = nd_group.get_uuid();
157
158 if reload_groups.insert(uuid, scope_i).is_some() {
159 error!("dyngroup cache uuid conflict {}", uuid);
160 return Err(OperationError::InvalidState);
161 }
162 }
163
164 let dyn_groups = qs.get_dyngroup_cache();
165 std::mem::swap(&mut reload_groups, &mut dyn_groups.insts);
166
167 Ok(())
168 }
169
170 #[instrument(level = "debug", name = "dyngroup::post_create", skip_all)]
171 pub fn post_create(
172 qs: &mut QueryServerWriteTransaction,
173 cand: &[Entry<EntrySealed, EntryCommitted>],
174 _ident: &Identity,
175 ) -> Result<BTreeSet<Uuid>, OperationError> {
176 let mut affected_uuids = BTreeSet::new();
177
178 if qs.get_phase() < ServerPhase::SchemaReady {
179 debug!("Server is not ready to apply dyngroups");
180 return Ok(affected_uuids);
181 }
182
183 let ident_internal = Identity::from_internal();
184
185 let (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| {
186 entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
187 });
188
189 let dyn_groups: &mut DynGroupCache = unsafe { &mut *(qs.get_dyngroup_cache() as *mut _) };
196
197 let mut candidate_tuples = Vec::with_capacity(cand.len());
203
204 trace!(?dyn_groups.insts);
206 for (dg_uuid, dg_filter) in dyn_groups.insts.iter() {
207 let dg_filter_valid = dg_filter
208 .validate(qs.get_schema())
209 .map_err(OperationError::SchemaViolation)
210 .and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
211
212 let matches: Vec<_> = entries
214 .iter()
215 .filter_map(|e| {
216 if e.entry_match_no_index(&dg_filter_valid) {
217 Some(e.get_uuid())
218 } else {
219 None
220 }
221 })
222 .collect();
223
224 if !matches.is_empty() {
227 let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(*dg_uuid)));
228 let mut work_set = qs.internal_search_writeable(&filt)?;
229
230 if let Some((pre, mut d_group)) = work_set.pop() {
231 matches
232 .iter()
233 .copied()
234 .for_each(|u| d_group.add_ava(Attribute::DynMember, Value::Refer(u)));
235
236 let pre_dynmember = pre.get_ava_refer(Attribute::DynMember);
241 let post_dynmember = d_group.get_ava_refer(Attribute::DynMember);
242
243 match (pre_dynmember, post_dynmember) {
244 (Some(pre_m), Some(post_m)) => {
245 affected_uuids.extend(pre_m.symmetric_difference(post_m));
247 }
248 (Some(members), None) | (None, Some(members)) => {
249 affected_uuids.extend(members);
251 }
252 (None, None) => {}
253 };
254
255 candidate_tuples.push((pre, d_group));
256 }
257 }
258 }
259
260 if !candidate_tuples.is_empty() {
263 qs.internal_apply_writable(candidate_tuples).map_err(|e| {
264 error!("Failed to commit dyngroup set {:?}", e);
265 e
266 })?;
267 }
268
269 if !n_dyn_groups.is_empty() {
273 trace!("considering new dyngroups");
274 Self::apply_dyngroup_change(
275 qs,
276 &mut affected_uuids,
277 false,
278 &ident_internal,
279 dyn_groups,
280 n_dyn_groups.as_slice(),
281 )?;
282 }
283
284 Ok(affected_uuids)
285 }
286
287 #[instrument(level = "debug", name = "dyngroup::post_modify", skip_all)]
288 pub fn post_modify(
289 qs: &mut QueryServerWriteTransaction,
290 pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
291 cand: &[Entry<EntrySealed, EntryCommitted>],
292 _ident: &Identity,
293 force_cand_updates: bool,
294 ) -> Result<BTreeSet<Uuid>, OperationError> {
295 let mut affected_uuids = BTreeSet::new();
296
297 if qs.get_phase() < ServerPhase::SchemaReady {
298 debug!("Server is not ready to apply dyngroups");
299 return Ok(affected_uuids);
300 }
301
302 let ident_internal = Identity::from_internal();
303
304 let (_, pre_entries): (Vec<&Arc<Entry<_, _>>>, Vec<_>) =
306 pre_cand.iter().partition(|entry| {
307 entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
308 });
309
310 let (n_dyn_groups, post_entries): (Vec<&Entry<_, _>>, Vec<_>) =
311 cand.iter().partition(|entry| {
312 entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
313 });
314
315 let dyn_groups: &mut DynGroupCache = unsafe { &mut *(qs.get_dyngroup_cache() as *mut _) };
322
323 let mut candidate_tuples = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
324
325 if !n_dyn_groups.is_empty() {
331 Self::apply_dyngroup_change(
332 qs,
333 &mut affected_uuids,
334 true,
335 &ident_internal,
336 dyn_groups,
337 n_dyn_groups.as_slice(),
338 )?;
339 }
340
341 trace!(?force_cand_updates, ?dyn_groups.insts);
344
345 for (dg_uuid, dg_filter) in dyn_groups.insts.iter() {
346 let dg_filter_valid = dg_filter
347 .validate(qs.get_schema())
348 .map_err(OperationError::SchemaViolation)
349 .and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
350
351 let matches: Vec<_> = pre_entries
352 .iter()
353 .zip(post_entries.iter())
354 .filter_map(|(pre, post)| {
355 let pre_t = pre.entry_match_no_index(&dg_filter_valid);
356 let post_t = post.entry_match_no_index(&dg_filter_valid);
357
358 trace!(?post_t, ?force_cand_updates, ?pre_t);
359
360 if post_t && (force_cand_updates || !pre_t) {
365 Some(Ok(post.get_uuid()))
367 } else if pre_t && !post_t {
368 Some(Err(post.get_uuid()))
370 } else {
371 None
372 }
373 })
374 .collect();
375
376 trace!(?matches);
377
378 if !matches.is_empty() {
379 let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(*dg_uuid)));
380 let mut work_set = qs.internal_search_writeable(&filt)?;
381
382 if let Some((pre, mut d_group)) = work_set.pop() {
383 matches.iter().copied().for_each(|choice| match choice {
384 Ok(u) => d_group.add_ava(Attribute::DynMember, Value::Refer(u)),
385 Err(u) => d_group.remove_ava(Attribute::DynMember, &PartialValue::Refer(u)),
386 });
387
388 let pre_dynmember = pre.get_ava_refer(Attribute::DynMember);
392 let post_dynmember = d_group.get_ava_refer(Attribute::DynMember);
393
394 match (pre_dynmember, post_dynmember) {
395 (Some(pre_m), Some(post_m)) => {
396 affected_uuids.extend(pre_m.symmetric_difference(post_m));
398 }
399 (Some(members), None) | (None, Some(members)) => {
400 affected_uuids.extend(members);
402 }
403 (None, None) => {}
404 };
405
406 candidate_tuples.push((pre, d_group));
407 }
408 }
409 }
410
411 trace!(candidate_tuples_len = %candidate_tuples.len());
414 if !candidate_tuples.is_empty() {
415 qs.internal_apply_writable(candidate_tuples).map_err(|e| {
416 error!("Failed to commit dyngroup set {:?}", e);
417 e
418 })?;
419 }
420
421 trace!(?affected_uuids);
422
423 Ok(affected_uuids)
424 }
425
426 pub fn verify(_qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
429 vec![]
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use kanidm_proto::internal::Filter as ProtoFilter;
436
437 use crate::prelude::*;
438
439 const UUID_TEST_GROUP: Uuid = uuid::uuid!("7bfd9931-06c2-4608-8a46-78719bb746fe");
440
441 #[test]
442 fn test_create_dyngroup_add_new_group() {
443 let e_dyn = entry_init!(
444 (Attribute::Class, EntryClass::Object.to_value()),
445 (Attribute::Class, EntryClass::Group.to_value()),
446 (Attribute::Class, EntryClass::DynGroup.to_value()),
447 (Attribute::Name, Value::new_iname("test_dyngroup")),
448 (
449 Attribute::DynGroupFilter,
450 Value::JsonFilt(ProtoFilter::Eq(
451 Attribute::Name.to_string(),
452 "testgroup".to_string()
453 ))
454 )
455 );
456
457 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
458 (Attribute::Class, EntryClass::Group.to_value()),
459 (Attribute::Name, Value::new_iname("testgroup")),
460 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
461 );
462
463 let preload = vec![e_group];
464 let create = vec![e_dyn];
465
466 run_create_test!(
467 Ok(()),
468 preload,
469 create,
470 None,
471 |qs: &mut QueryServerWriteTransaction| {
473 let cands = qs
474 .internal_search(filter!(f_eq(
475 Attribute::Name,
476 PartialValue::new_iname("test_dyngroup")
477 )))
478 .expect("Internal search failure");
479
480 let d_group = cands.first().expect("Unable to access group.");
481 let members = d_group
482 .get_ava_set(Attribute::DynMember)
483 .expect("No members on dyn group");
484
485 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
486 }
487 );
488 }
489
490 #[test]
491 fn test_create_dyngroup_add_matching_entry() {
492 let e_dyn = entry_init!(
493 (Attribute::Class, EntryClass::Object.to_value()),
494 (Attribute::Class, EntryClass::Group.to_value()),
495 (Attribute::Class, EntryClass::DynGroup.to_value()),
496 (Attribute::Name, Value::new_iname("test_dyngroup")),
497 (
498 Attribute::DynGroupFilter,
499 Value::JsonFilt(ProtoFilter::Eq(
500 Attribute::Name.to_string(),
501 "testgroup".to_string()
502 ))
503 )
504 );
505
506 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
507 (Attribute::Class, EntryClass::Group.to_value()),
508 (Attribute::Name, Value::new_iname("testgroup")),
509 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
510 );
511
512 let preload = vec![e_dyn];
513 let create = vec![e_group];
514
515 run_create_test!(
516 Ok(()),
517 preload,
518 create,
519 None,
520 |qs: &mut QueryServerWriteTransaction| {
522 let cands = qs
523 .internal_search(filter!(f_eq(
524 Attribute::Name,
525 PartialValue::new_iname("test_dyngroup")
526 )))
527 .expect("Internal search failure");
528
529 let d_group = cands.first().expect("Unable to access group.");
530 let members = d_group
531 .get_ava_set(Attribute::DynMember)
532 .expect("No members on dyn group");
533
534 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
535 }
536 );
537 }
538
539 #[test]
540 fn test_create_dyngroup_add_non_matching_entry() {
541 let e_dyn = entry_init!(
542 (Attribute::Class, EntryClass::Object.to_value()),
543 (Attribute::Class, EntryClass::Group.to_value()),
544 (Attribute::Class, EntryClass::DynGroup.to_value()),
545 (Attribute::Name, Value::new_iname("test_dyngroup")),
546 (
547 Attribute::DynGroupFilter,
548 Value::JsonFilt(ProtoFilter::Eq(
549 Attribute::Name.to_string(),
550 "no_possible_match_to_be_found".to_string()
551 ))
552 )
553 );
554
555 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
556 (Attribute::Class, EntryClass::Group.to_value()),
557 (Attribute::Name, Value::new_iname("testgroup")),
558 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
559 );
560
561 let preload = vec![e_dyn];
562 let create = vec![e_group];
563
564 run_create_test!(
565 Ok(()),
566 preload,
567 create,
568 None,
569 |qs: &mut QueryServerWriteTransaction| {
571 let cands = qs
572 .internal_search(filter!(f_eq(
573 Attribute::Name,
574 PartialValue::new_iname("test_dyngroup")
575 )))
576 .expect("Internal search failure");
577
578 let d_group = cands.first().expect("Unable to access group.");
579 assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
580 }
581 );
582 }
583
584 #[test]
585 fn test_create_dyngroup_add_matching_entry_and_group() {
586 let e_dyn = entry_init!(
587 (Attribute::Class, EntryClass::Object.to_value()),
588 (Attribute::Class, EntryClass::Group.to_value()),
589 (Attribute::Class, EntryClass::DynGroup.to_value()),
590 (Attribute::Name, Value::new_iname("test_dyngroup")),
591 (
592 Attribute::DynGroupFilter,
593 Value::JsonFilt(ProtoFilter::Eq(
594 Attribute::Name.to_string(),
595 "testgroup".to_string()
596 ))
597 )
598 );
599
600 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
601 (Attribute::Class, EntryClass::Group.to_value()),
602 (Attribute::Name, Value::new_iname("testgroup")),
603 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
604 );
605
606 let preload = vec![];
607 let create = vec![e_dyn, e_group];
608
609 run_create_test!(
610 Ok(()),
611 preload,
612 create,
613 None,
614 |qs: &mut QueryServerWriteTransaction| {
616 let cands = qs
617 .internal_search(filter!(f_eq(
618 Attribute::Name,
619 PartialValue::new_iname("test_dyngroup")
620 )))
621 .expect("Internal search failure");
622
623 let d_group = cands.first().expect("Unable to access group.");
624 let members = d_group
625 .get_ava_set(Attribute::DynMember)
626 .expect("No members on dyn group");
627
628 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
629 assert!(d_group.get_ava_set(Attribute::Member).is_none());
630 }
631 );
632 }
633
634 #[test]
635 fn test_modify_dyngroup_existing_dyngroup_filter_into_scope() {
636 let e_dyn = entry_init!(
637 (Attribute::Class, EntryClass::Object.to_value()),
638 (Attribute::Class, EntryClass::Group.to_value()),
639 (Attribute::Class, EntryClass::DynGroup.to_value()),
640 (Attribute::Name, Value::new_iname("test_dyngroup")),
641 (
642 Attribute::DynGroupFilter,
643 Value::JsonFilt(ProtoFilter::Eq(
644 Attribute::Name.to_string(),
645 "no_such_entry_exists".to_string()
646 ))
647 )
648 );
649
650 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
651 (Attribute::Class, EntryClass::Group.to_value()),
652 (Attribute::Name, Value::new_iname("testgroup")),
653 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
654 );
655
656 let preload = vec![e_dyn, e_group];
657
658 run_modify_test!(
659 Ok(()),
660 preload,
661 filter!(f_eq(
662 Attribute::Name,
663 PartialValue::new_iname("test_dyngroup")
664 )),
665 ModifyList::new_list(vec![
666 Modify::Purged("dyngroup_filter".into()),
667 Modify::Present(
668 Attribute::DynGroupFilter,
669 Value::JsonFilt(ProtoFilter::Eq(
670 Attribute::Name.to_string(),
671 "testgroup".to_string()
672 ))
673 )
674 ]),
675 None,
676 |_| {},
677 |qs: &mut QueryServerWriteTransaction| {
678 let cands = qs
679 .internal_search(filter!(f_eq(
680 Attribute::Name,
681 PartialValue::new_iname("test_dyngroup")
682 )))
683 .expect("Internal search failure");
684
685 let d_group = cands.first().expect("Unable to access group.");
686 let members = d_group
687 .get_ava_set(Attribute::DynMember)
688 .expect("No members on dyn group");
689
690 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
691 }
692 );
693 }
694
695 #[test]
696 fn test_modify_dyngroup_existing_dyngroup_filter_outof_scope() {
697 let e_dyn = entry_init!(
698 (Attribute::Class, EntryClass::Object.to_value()),
699 (Attribute::Class, EntryClass::Group.to_value()),
700 (Attribute::Class, EntryClass::DynGroup.to_value()),
701 (Attribute::Name, Value::new_iname("test_dyngroup")),
702 (
703 Attribute::DynGroupFilter,
704 Value::JsonFilt(ProtoFilter::Eq(
705 Attribute::Name.to_string(),
706 "testgroup".to_string()
707 ))
708 )
709 );
710
711 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
712 (Attribute::Class, EntryClass::Group.to_value()),
713 (Attribute::Name, Value::new_iname("testgroup")),
714 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
715 );
716
717 let preload = vec![e_dyn, e_group];
718
719 run_modify_test!(
720 Ok(()),
721 preload,
722 filter!(f_eq(
723 Attribute::Name,
724 PartialValue::new_iname("test_dyngroup")
725 )),
726 ModifyList::new_list(vec![
727 Modify::Purged("dyngroup_filter".into()),
728 Modify::Present(
729 Attribute::DynGroupFilter,
730 Value::JsonFilt(ProtoFilter::Eq(
731 Attribute::Name.to_string(),
732 "no_such_entry_exists".to_string()
733 ))
734 )
735 ]),
736 None,
737 |_| {},
738 |qs: &mut QueryServerWriteTransaction| {
739 let cands = qs
740 .internal_search(filter!(f_eq(
741 Attribute::Name,
742 PartialValue::new_iname("test_dyngroup")
743 )))
744 .expect("Internal search failure");
745
746 let d_group = cands.first().expect("Unable to access group.");
747 assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
748 }
749 );
750 }
751
752 #[test]
753 fn test_modify_dyngroup_existing_dyngroup_member_add() {
754 let e_dyn = entry_init!(
755 (Attribute::Class, EntryClass::Object.to_value()),
756 (Attribute::Class, EntryClass::Group.to_value()),
757 (Attribute::Class, EntryClass::DynGroup.to_value()),
758 (Attribute::Name, Value::new_iname("test_dyngroup")),
759 (
760 Attribute::DynGroupFilter,
761 Value::JsonFilt(ProtoFilter::Eq(
762 Attribute::Name.to_string(),
763 "testgroup".to_string()
764 ))
765 )
766 );
767
768 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
769 (Attribute::Class, EntryClass::Group.to_value()),
770 (Attribute::Name, Value::new_iname("testgroup")),
771 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
772 );
773
774 let preload = vec![e_dyn, e_group];
775
776 run_modify_test!(
777 Ok(()),
778 preload,
779 filter!(f_eq(
780 Attribute::Name,
781 PartialValue::new_iname("test_dyngroup")
782 )),
783 ModifyList::new_list(vec![Modify::Present(
784 Attribute::DynMember,
785 Value::Refer(UUID_ADMIN)
786 )]),
787 None,
788 |_| {},
789 |qs: &mut QueryServerWriteTransaction| {
790 let cands = qs
791 .internal_search(filter!(f_eq(
792 Attribute::Name,
793 PartialValue::new_iname("test_dyngroup")
794 )))
795 .expect("Internal search failure");
796
797 let d_group = cands.first().expect("Unable to access group.");
798 let members = d_group
799 .get_ava_set(Attribute::DynMember)
800 .expect("No members on dyn group");
801 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
804 }
805 );
806 }
807
808 #[test]
809 fn test_modify_dyngroup_existing_dyngroup_member_remove() {
810 let e_dyn = entry_init!(
811 (Attribute::Class, EntryClass::Object.to_value()),
812 (Attribute::Class, EntryClass::Group.to_value()),
813 (Attribute::Class, EntryClass::DynGroup.to_value()),
814 (Attribute::Name, Value::new_iname("test_dyngroup")),
815 (
816 Attribute::DynGroupFilter,
817 Value::JsonFilt(ProtoFilter::Eq(
818 Attribute::Name.to_string(),
819 "testgroup".to_string()
820 ))
821 )
822 );
823
824 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
825 (Attribute::Class, EntryClass::Group.to_value()),
826 (Attribute::Name, Value::new_iname("testgroup")),
827 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
828 );
829
830 let preload = vec![e_dyn, e_group];
831
832 run_modify_test!(
833 Ok(()),
834 preload,
835 filter!(f_eq(
836 Attribute::Name,
837 PartialValue::new_iname("test_dyngroup")
838 )),
839 ModifyList::new_list(vec![Modify::Purged(Attribute::DynMember,)]),
840 None,
841 |_| {},
842 |qs: &mut QueryServerWriteTransaction| {
843 let cands = qs
844 .internal_search(filter!(f_eq(
845 Attribute::Name,
846 PartialValue::new_iname("test_dyngroup")
847 )))
848 .expect("Internal search failure");
849
850 let d_group = cands.first().expect("Unable to access group.");
851 let members = d_group
852 .get_ava_set(Attribute::DynMember)
853 .expect("No members on dyn group");
854 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
856 }
857 );
858 }
859
860 #[test]
861 fn test_modify_dyngroup_into_matching_entry() {
862 let e_dyn = entry_init!(
863 (Attribute::Class, EntryClass::Object.to_value()),
864 (Attribute::Class, EntryClass::Group.to_value()),
865 (Attribute::Class, EntryClass::DynGroup.to_value()),
866 (Attribute::Name, Value::new_iname("test_dyngroup")),
867 (
868 Attribute::DynGroupFilter,
869 Value::JsonFilt(ProtoFilter::Eq(
870 Attribute::Name.to_string(),
871 "testgroup".to_string()
872 ))
873 )
874 );
875
876 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
877 (Attribute::Class, EntryClass::Group.to_value()),
878 (Attribute::Name, Value::new_iname("not_testgroup")),
879 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
880 );
881
882 let preload = vec![e_dyn, e_group];
883
884 run_modify_test!(
885 Ok(()),
886 preload,
887 filter!(f_eq(
888 Attribute::Name,
889 PartialValue::new_iname("not_testgroup")
890 )),
891 ModifyList::new_list(vec![
892 Modify::Purged(Attribute::Name,),
893 Modify::Present(Attribute::Name, Value::new_iname("testgroup"))
894 ]),
895 None,
896 |_| {},
897 |qs: &mut QueryServerWriteTransaction| {
898 let cands = qs
899 .internal_search(filter!(f_eq(
900 Attribute::Name,
901 PartialValue::new_iname("test_dyngroup")
902 )))
903 .expect("Internal search failure");
904
905 let d_group = cands.first().expect("Unable to access group.");
906 let members = d_group
907 .get_ava_set(Attribute::DynMember)
908 .expect("No members on dyn group");
909
910 assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
911 }
912 );
913 }
914
915 #[test]
916 fn test_modify_dyngroup_into_non_matching_entry() {
917 let e_dyn = entry_init!(
918 (Attribute::Class, EntryClass::Object.to_value()),
919 (Attribute::Class, EntryClass::Group.to_value()),
920 (Attribute::Class, EntryClass::DynGroup.to_value()),
921 (Attribute::Name, Value::new_iname("test_dyngroup")),
922 (
923 Attribute::DynGroupFilter,
924 Value::JsonFilt(ProtoFilter::Eq(
925 Attribute::Name.to_string(),
926 "testgroup".to_string()
927 ))
928 )
929 );
930
931 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
932 (Attribute::Class, EntryClass::Group.to_value()),
933 (Attribute::Name, Value::new_iname("testgroup")),
934 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
935 );
936
937 let preload = vec![e_dyn, e_group];
938
939 run_modify_test!(
940 Ok(()),
941 preload,
942 filter!(f_eq(Attribute::Name, PartialValue::new_iname("testgroup"))),
943 ModifyList::new_list(vec![
944 Modify::Purged(Attribute::Name,),
945 Modify::Present(Attribute::Name, Value::new_iname("not_testgroup"))
946 ]),
947 None,
948 |_| {},
949 |qs: &mut QueryServerWriteTransaction| {
950 let cands = qs
951 .internal_search(filter!(f_eq(
952 Attribute::Name,
953 PartialValue::new_iname("test_dyngroup")
954 )))
955 .expect("Internal search failure");
956
957 let d_group = cands.first().expect("Unable to access group.");
958 assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
959 }
960 );
961 }
962
963 #[test]
964 fn test_delete_dyngroup_matching_entry() {
965 let e_dyn = entry_init!(
966 (Attribute::Class, EntryClass::Object.to_value()),
967 (Attribute::Class, EntryClass::Group.to_value()),
968 (Attribute::Class, EntryClass::DynGroup.to_value()),
969 (Attribute::Name, Value::new_iname("test_dyngroup")),
970 (
971 Attribute::DynGroupFilter,
972 Value::JsonFilt(ProtoFilter::Eq(
973 Attribute::Name.to_string(),
974 "testgroup".to_string()
975 ))
976 )
977 );
978
979 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
980 (Attribute::Class, EntryClass::Group.to_value()),
981 (Attribute::Name, Value::new_iname("testgroup")),
982 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
983 );
984
985 let preload = vec![e_dyn, e_group];
986
987 run_delete_test!(
988 Ok(()),
989 preload,
990 filter!(f_eq(Attribute::Name, PartialValue::new_iname("testgroup"))),
991 None,
992 |qs: &mut QueryServerWriteTransaction| {
993 let cands = qs
994 .internal_search(filter!(f_eq(
995 Attribute::Name,
996 PartialValue::new_iname("test_dyngroup")
997 )))
998 .expect("Internal search failure");
999
1000 let d_group = cands.first().expect("Unable to access group.");
1001 assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
1002 }
1003 );
1004 }
1005
1006 #[test]
1007 fn test_delete_dyngroup_group() {
1008 let e_dyn = entry_init!(
1009 (Attribute::Class, EntryClass::Object.to_value()),
1010 (Attribute::Class, EntryClass::Group.to_value()),
1011 (Attribute::Class, EntryClass::DynGroup.to_value()),
1012 (Attribute::Name, Value::new_iname("test_dyngroup")),
1013 (
1014 Attribute::DynGroupFilter,
1015 Value::JsonFilt(ProtoFilter::Eq(
1016 Attribute::Name.to_string(),
1017 "testgroup".to_string()
1018 ))
1019 )
1020 );
1021
1022 let e_group: Entry<EntryInit, EntryNew> = entry_init!(
1023 (Attribute::Class, EntryClass::Group.to_value()),
1024 (Attribute::Name, Value::new_iname("testgroup")),
1025 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
1026 );
1027
1028 let preload = vec![e_dyn, e_group];
1029
1030 run_delete_test!(
1031 Ok(()),
1032 preload,
1033 filter!(f_eq(
1034 Attribute::Name,
1035 PartialValue::new_iname("test_dyngroup")
1036 )),
1037 None,
1038 |qs: &mut QueryServerWriteTransaction| {
1039 let cands = qs
1041 .internal_search(filter!(f_eq(
1042 Attribute::Name,
1043 PartialValue::new_iname("testgroup")
1044 )))
1045 .expect("Internal search failure");
1046
1047 let d_group = cands.first().expect("Unable to access group.");
1048 assert!(d_group.get_ava_set(Attribute::MemberOf).is_none());
1049 }
1050 );
1051 }
1052}