1use hashbrown::HashMap;
17use std::cell::Cell;
18use std::collections::BTreeSet;
19use std::ops::DerefMut;
20use std::sync::Arc;
21
22use concread::arcache::ARCacheBuilder;
23use concread::cowcell::*;
24use uuid::Uuid;
25
26use crate::entry::{Entry, EntryInit, EntryNew};
27use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent};
28use crate::filter::{Filter, FilterValid, ResolveFilterCache, ResolveFilterCacheReadTxn};
29use crate::modify::Modify;
30use crate::prelude::*;
31
32use self::profiles::{
33 AccessControlCreate, AccessControlCreateResolved, AccessControlDelete,
34 AccessControlDeleteResolved, AccessControlModify, AccessControlModifyResolved,
35 AccessControlReceiver, AccessControlReceiverCondition, AccessControlSearch,
36 AccessControlSearchResolved, AccessControlTarget, AccessControlTargetCondition,
37};
38
39use kanidm_proto::scim_v1::server::ScimAttributeEffectiveAccess;
40
41use self::create::{apply_create_access, CreateResult};
42use self::delete::{apply_delete_access, DeleteResult};
43use self::modify::{apply_modify_access, ModifyResult};
44use self::search::{apply_search_access, SearchResult};
45
46const ACP_RESOLVE_FILTER_CACHE_MAX: usize = 256;
47const ACP_RESOLVE_FILTER_CACHE_LOCAL: usize = 0;
48
49mod create;
50mod delete;
51mod migration;
52mod modify;
53pub mod profiles;
54mod protected;
55mod search;
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum Access {
59 Grant,
60 Deny,
61 Allow(BTreeSet<Attribute>),
62}
63
64impl From<&Access> for ScimAttributeEffectiveAccess {
65 fn from(value: &Access) -> Self {
66 match value {
67 Access::Grant => Self::Grant,
68 Access::Deny => Self::Deny,
69 Access::Allow(set) => Self::Allow(set.clone()),
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum AccessClass {
76 Grant,
77 Deny,
78 Allow(BTreeSet<AttrString>),
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct AccessEffectivePermission {
83 pub ident: Uuid,
85 pub target: Uuid,
87 pub delete: bool,
88 pub search: Access,
89 pub modify_pres: Access,
90 pub modify_rem: Access,
91 pub modify_pres_class: AccessClass,
92 pub modify_rem_class: AccessClass,
93}
94
95pub enum AccessBasicResult {
96 Deny,
98 Grant,
100 Ignore,
102}
103
104pub enum AccessSrchResult {
105 Deny,
107 Grant,
109 Ignore,
111 Allow { attr: BTreeSet<Attribute> },
120}
121
122pub enum AccessModResult<'a> {
123 Deny,
125 Ignore,
129 Constrain {
133 pres_attr: BTreeSet<Attribute>,
134 rem_attr: BTreeSet<Attribute>,
135 pres_cls: Option<BTreeSet<&'a str>>,
136 rem_cls: Option<BTreeSet<&'a str>>,
137 },
138 Allow {
140 pres_attr: BTreeSet<Attribute>,
141 rem_attr: BTreeSet<Attribute>,
142 pres_class: BTreeSet<&'a str>,
143 rem_class: BTreeSet<&'a str>,
144 },
145}
146
147#[derive(Clone)]
152struct AccessControlsInner {
153 acps_search: Vec<AccessControlSearch>,
154 acps_create: Vec<AccessControlCreate>,
155 acps_modify: Vec<AccessControlModify>,
156 acps_delete: Vec<AccessControlDelete>,
157 sync_agreements: HashMap<Uuid, BTreeSet<Attribute>>,
158 }
161
162pub struct AccessControls {
163 inner: CowCell<AccessControlsInner>,
164 acp_resolve_filter_cache: ResolveFilterCache,
166}
167
168fn resolve_access_conditions(
169 ident: &Identity,
170 ident_memberof: Option<&BTreeSet<Uuid>>,
171 receiver: &AccessControlReceiver,
172 target: &AccessControlTarget,
173 acp_resolve_filter_cache: &mut ResolveFilterCacheReadTxn<'_>,
174) -> Option<(AccessControlReceiverCondition, AccessControlTargetCondition)> {
175 let receiver_condition = match receiver {
176 AccessControlReceiver::Group(groups) => {
177 let group_check = ident_memberof
178 .map(|imo| {
180 trace!(?imo, ?groups);
181 imo.intersection(groups).next().is_some()
182 })
183 .unwrap_or_default();
184
185 if group_check {
186 AccessControlReceiverCondition::GroupChecked
187 } else {
188 return None;
190 }
191 }
192 AccessControlReceiver::EntryManager => AccessControlReceiverCondition::EntryManager,
193 AccessControlReceiver::None => return None,
194 };
196
197 let target_condition = match &target {
198 AccessControlTarget::Scope(filter) => filter
199 .resolve(ident, None, Some(acp_resolve_filter_cache))
200 .map_err(|e| {
201 admin_error!(?e, "A internal filter/event was passed for resolution!?!?");
202 e
203 })
204 .ok()
205 .map(AccessControlTargetCondition::Scope)?,
206 AccessControlTarget::None => return None,
207 };
208
209 Some((receiver_condition, target_condition))
210}
211
212pub trait AccessControlsTransaction<'a> {
213 fn get_search(&self) -> &Vec<AccessControlSearch>;
214 fn get_create(&self) -> &Vec<AccessControlCreate>;
215 fn get_modify(&self) -> &Vec<AccessControlModify>;
216 fn get_delete(&self) -> &Vec<AccessControlDelete>;
217 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>>;
218
219 #[allow(clippy::mut_from_ref)]
220 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a>;
221
222 #[instrument(level = "trace", name = "access::search_related_acp", skip_all)]
223 fn search_related_acp<'b>(
224 &'b self,
225 ident: &Identity,
226 attrs: Option<&BTreeSet<Attribute>>,
227 ) -> Vec<AccessControlSearchResolved<'b>> {
228 let search_state = self.get_search();
229 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
230
231 let ident_memberof = ident.get_memberof();
259
260 let related_acp: Vec<AccessControlSearchResolved<'b>> = search_state
262 .iter()
263 .filter_map(|acs| {
264 let (receiver_condition, target_condition) = resolve_access_conditions(
280 ident,
281 ident_memberof,
282 &acs.acp.receiver,
283 &acs.acp.target,
284 acp_resolve_filter_cache,
285 )?;
286
287 Some(AccessControlSearchResolved {
288 acp: acs,
289 receiver_condition,
290 target_condition,
291 })
292 })
293 .collect();
294
295 let related_acp = if let Some(r_attrs) = attrs.as_ref() {
297 related_acp
298 .into_iter()
299 .filter(|acs| !acs.acp.attrs.is_disjoint(r_attrs))
300 .collect()
301 } else {
302 related_acp
304 };
305
306 related_acp
307 }
308
309 #[instrument(level = "debug", name = "access::filter_entries", skip_all)]
310 fn filter_entries(
311 &self,
312 ident: &Identity,
313 filter_orig: &Filter<FilterValid>,
314 entries: Vec<Arc<EntrySealedCommitted>>,
315 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
316 let requested_attrs: BTreeSet<Attribute> = filter_orig.get_attr_set();
321
322 let related_acp = self.search_related_acp(ident, None);
324
325 let entries_is_empty = entries.is_empty();
327 let allowed_entries: Vec<_> = entries
328 .into_iter()
329 .filter(|e| {
330 match apply_search_access(ident, related_acp.as_slice(), e) {
331 SearchResult::Deny => false,
332 SearchResult::Grant => true,
333 SearchResult::Allow(allowed_attrs) => {
334 let decision = requested_attrs.is_subset(&allowed_attrs);
336 security_debug!(
337 ?decision,
338 allowed = ?allowed_attrs,
339 requested = ?requested_attrs,
340 "search attribute decision",
341 );
342 decision
343 }
344 }
345 })
346 .collect();
347
348 if allowed_entries.is_empty() {
349 if !entries_is_empty {
350 security_access!("denied ❌ - no entries were released");
351 }
352 } else {
353 debug!("allowed search of {} entries ✅", allowed_entries.len());
354 }
355
356 Ok(allowed_entries)
357 }
358
359 #[inline(always)]
361 fn search_filter_entries(
362 &self,
363 se: &SearchEvent,
364 entries: Vec<Arc<EntrySealedCommitted>>,
365 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
366 self.filter_entries(&se.ident, &se.filter_orig, entries)
367 }
368
369 #[instrument(
370 level = "debug",
371 name = "access::search_filter_entry_attributes",
372 skip_all
373 )]
374 fn search_filter_entry_attributes(
375 &self,
376 se: &SearchEvent,
377 entries: Vec<Arc<EntrySealedCommitted>>,
378 ) -> Result<Vec<EntryReducedCommitted>, OperationError> {
379 struct DoEffectiveCheck<'b> {
380 modify_related_acp: Vec<AccessControlModifyResolved<'b>>,
381 delete_related_acp: Vec<AccessControlDeleteResolved<'b>>,
382 sync_agmts: &'b HashMap<Uuid, BTreeSet<Attribute>>,
383 }
384
385 let ident_uuid = match &se.ident.origin {
386 IdentType::Internal(_) => {
387 security_critical!("IMPOSSIBLE STATE: Internal search in external interface?! Returning empty for safety.");
390 return Err(OperationError::InvalidState);
392 }
393 IdentType::Synch(_) => {
394 security_critical!("Blocking sync check");
395 return Err(OperationError::InvalidState);
396 }
397 IdentType::User(u) => u.entry.get_uuid(),
398 };
399
400 let do_effective_check = se.effective_access_check.then(|| {
404 debug!("effective permission check requested during reduction phase");
405
406 let modify_related_acp = self.modify_related_acp(&se.ident);
408 let delete_related_acp = self.delete_related_acp(&se.ident);
410
411 let sync_agmts = self.get_sync_agreements();
412
413 DoEffectiveCheck {
414 modify_related_acp,
415 delete_related_acp,
416 sync_agmts,
417 }
418 });
419
420 let search_related_acp = self.search_related_acp(&se.ident, se.attrs.as_ref());
422
423 let entries_is_empty = entries.is_empty();
425 let allowed_entries: Vec<_> = entries
426 .into_iter()
427 .filter_map(|entry| {
428 match apply_search_access(&se.ident, &search_related_acp, &entry) {
429 SearchResult::Deny => {
430 None
431 }
432 SearchResult::Grant => {
433 error!("An access module allowed full read, this is a BUG! Denying read to prevent data leaks.");
436 None
437 }
438 SearchResult::Allow(allowed_attrs) => {
439 debug!(
441 requested = ?se.attrs,
442 allowed = ?allowed_attrs,
443 "reduction",
444 );
445
446 let reduced_attrs = if let Some(requested) = se.attrs.as_ref() {
448 requested & &allowed_attrs
449 } else {
450 allowed_attrs
451 };
452
453 let effective_permissions = do_effective_check.as_ref().map(|do_check| {
454 self.entry_effective_permission_check(
455 &se.ident,
456 ident_uuid,
457 &entry,
458 &search_related_acp,
459 &do_check.modify_related_acp,
460 &do_check.delete_related_acp,
461 do_check.sync_agmts,
462 )
463 })
464 .map(Box::new);
465
466 Some(entry.reduce_attributes(&reduced_attrs, effective_permissions))
467 }
468 }
469
470 })
472 .collect();
473
474 if allowed_entries.is_empty() {
475 if !entries_is_empty {
476 security_access!("reduced to empty set on all entries ❌");
477 }
478 } else {
479 debug!(
480 "attribute set reduced on {} entries ✅",
481 allowed_entries.len()
482 );
483 }
484
485 Ok(allowed_entries)
486 }
487
488 #[instrument(level = "trace", name = "access::modify_related_acp", skip_all)]
489 fn modify_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlModifyResolved<'b>> {
490 let modify_state = self.get_modify();
492 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
493
494 let ident_memberof = ident.get_memberof();
495
496 let related_acp: Vec<_> = modify_state
499 .iter()
500 .filter_map(|acs| {
501 trace!(acs_name = ?acs.acp.name);
502 let (receiver_condition, target_condition) = resolve_access_conditions(
503 ident,
504 ident_memberof,
505 &acs.acp.receiver,
506 &acs.acp.target,
507 acp_resolve_filter_cache,
508 )?;
509
510 Some(AccessControlModifyResolved {
511 acp: acs,
512 receiver_condition,
513 target_condition,
514 })
515 })
516 .collect();
517
518 related_acp
519 }
520
521 #[instrument(level = "debug", name = "access::modify_allow_operation", skip_all)]
522 fn modify_allow_operation(
523 &self,
524 me: &ModifyEvent,
525 entries: &[Arc<EntrySealedCommitted>],
526 ) -> Result<bool, OperationError> {
527 let disallow = me
529 .modlist
530 .iter()
531 .any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref()));
532
533 if disallow {
534 security_access!("Disallowing purge {} in modification", Attribute::Class);
535 return Ok(false);
536 }
537
538 let related_acp: Vec<_> = self.modify_related_acp(&me.ident);
541
542 let requested_pres: BTreeSet<Attribute> = me
544 .modlist
545 .iter()
546 .filter_map(|m| match m {
547 Modify::Present(a, _) | Modify::Set(a, _) => Some(a.clone()),
548 Modify::Removed(..) | Modify::Assert(..) | Modify::Purged(_) => None,
549 })
550 .collect();
551
552 let requested_rem: BTreeSet<Attribute> = me
553 .modlist
554 .iter()
555 .filter_map(|m| match m {
556 Modify::Set(a, _) | Modify::Removed(a, _) | Modify::Purged(a) => Some(a.clone()),
557 Modify::Present(..) | Modify::Assert(..) => None,
558 })
559 .collect();
560
561 let mut requested_pres_classes: BTreeSet<&str> = Default::default();
565 let mut requested_rem_classes: BTreeSet<&str> = Default::default();
566
567 for modify in me.modlist.iter() {
568 match modify {
569 Modify::Present(a, v) => {
570 if a == Attribute::Class.as_ref() {
571 requested_pres_classes.extend(v.to_str())
578 }
579 }
580 Modify::Removed(a, v) => {
581 if a == Attribute::Class.as_ref() {
582 requested_rem_classes.extend(v.to_str())
583 }
584 }
585 Modify::Set(a, v) => {
586 if a == Attribute::Class.as_ref() {
587 requested_pres_classes.extend(v.as_iutf8_iter().into_iter().flatten());
593 requested_rem_classes.extend(v.as_iutf8_iter().into_iter().flatten());
594 }
595 }
596 _ => {}
597 }
598 }
599
600 debug!(?requested_pres, "Requested present attribute set");
601 debug!(?requested_rem, "Requested remove attribute set");
602 debug!(?requested_pres_classes, "Requested present class set");
603 debug!(?requested_rem_classes, "Requested remove class set");
604
605 let sync_agmts = self.get_sync_agreements();
606
607 let r = entries.iter().all(|e| {
608 debug!(entry_id = %e.get_display_id());
609
610 match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
611 ModifyResult::Deny => false,
612 ModifyResult::Grant => true,
613 ModifyResult::Allow {
614 pres,
615 rem,
616 pres_cls,
617 rem_cls,
618 } => {
619 let mut decision = true;
620
621 if !requested_pres.is_subset(&pres) {
622 security_error!("requested_pres is not a subset of allowed");
623 security_error!(
624 "requested_pres: {:?} !⊆ allowed: {:?}",
625 requested_pres,
626 pres
627 );
628 decision = false
629 };
630
631 if !requested_rem.is_subset(&rem) {
632 security_error!("requested_rem is not a subset of allowed");
633 security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
634 decision = false;
635 };
636
637 if !requested_pres_classes.is_subset(&pres_cls) {
638 security_error!("requested_pres_classes is not a subset of allowed");
639 security_error!(
640 "requested_pres_classes: {:?} !⊆ allowed: {:?}",
641 requested_pres_classes,
642 pres_cls
643 );
644 decision = false;
645 };
646
647 if !requested_rem_classes.is_subset(&rem_cls) {
648 security_error!("requested_rem_classes is not a subset of allowed");
649 security_error!(
650 "requested_rem_classes: {:?} !⊆ allowed: {:?}",
651 requested_rem_classes,
652 rem_cls
653 );
654 decision = false;
655 }
656
657 if decision {
658 debug!("passed pres, rem, classes check.");
659 }
660
661 decision
663 }
664 }
665 });
666
667 if r {
668 debug!("allowed modify of {} entries ✅", entries.len());
669 } else {
670 security_access!("denied ❌ - modify may not proceed");
671 }
672 Ok(r)
673 }
674
675 #[instrument(
676 level = "debug",
677 name = "access::batch_modify_allow_operation",
678 skip_all
679 )]
680 fn batch_modify_allow_operation(
681 &self,
682 me: &BatchModifyEvent,
683 entries: &[Arc<EntrySealedCommitted>],
684 ) -> Result<bool, OperationError> {
685 let related_acp = self.modify_related_acp(&me.ident);
688
689 let r = entries.iter().all(|e| {
690 let Some(modlist) = me.modset.get(&e.get_uuid()) else {
694 security_access!(
695 "modlist not present for {}, failing operation.",
696 e.get_uuid()
697 );
698 return false;
699 };
700
701 let disallow = modlist
702 .iter()
703 .any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref()));
704
705 if disallow {
706 security_access!("Disallowing purge in modification");
707 return false;
708 }
709
710 let requested_pres: BTreeSet<Attribute> = modlist
712 .iter()
713 .filter_map(|m| match m {
714 Modify::Present(a, _) => Some(a.clone()),
715 _ => None,
716 })
717 .collect();
718
719 let requested_rem: BTreeSet<Attribute> = modlist
720 .iter()
721 .filter_map(|m| match m {
722 Modify::Removed(a, _) => Some(a.clone()),
723 Modify::Purged(a) => Some(a.clone()),
724 _ => None,
725 })
726 .collect();
727
728 let mut requested_pres_classes: BTreeSet<&str> = Default::default();
729 let mut requested_rem_classes: BTreeSet<&str> = Default::default();
730
731 for modify in modlist.iter() {
732 match modify {
733 Modify::Present(a, v) => {
734 if a == Attribute::Class.as_ref() {
735 requested_pres_classes.extend(v.to_str())
736 }
737 }
738 Modify::Removed(a, v) => {
739 if a == Attribute::Class.as_ref() {
740 requested_rem_classes.extend(v.to_str())
741 }
742 }
743 Modify::Set(a, v) => {
744 if a == Attribute::Class.as_ref() {
745 if let Some(current_classes) = e.get_ava_as_iutf8(Attribute::Class) {
748 if let Some(requested_classes) = v.as_iutf8_set() {
749 requested_pres_classes.extend( requested_classes.difference(current_classes).map(|s| s.as_str()) );
754 requested_rem_classes.extend( current_classes.difference(requested_classes).map(|s| s.as_str()) );
755 } else {
756 error!("invalid valueset state - requested class set is not valid");
762 return false;
763 }
764 } else {
765 error!("invalid entry state - entry does not have attribute class and is not valid");
766 return false;
767 }
768 }
769 }
770 _ => {}
771 }
772 }
773
774 debug!(?requested_pres, "Requested present set");
775 debug!(?requested_rem, "Requested remove set");
776 debug!(?requested_pres_classes, "Requested present class set");
777 debug!(?requested_rem_classes, "Requested remove class set");
778 debug!(entry_id = %e.get_display_id());
779
780 let sync_agmts = self.get_sync_agreements();
781
782 match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
783 ModifyResult::Deny => false,
784 ModifyResult::Grant => true,
785 ModifyResult::Allow {
786 pres,
787 rem,
788 pres_cls,
789 rem_cls,
790 } => {
791 let mut decision = true;
792
793 if !requested_pres.is_subset(&pres) {
794 security_error!("requested_pres is not a subset of allowed");
795 security_error!(
796 "requested_pres: {:?} !⊆ allowed: {:?}",
797 requested_pres,
798 pres
799 );
800 decision = false
801 };
802
803 if !requested_rem.is_subset(&rem) {
804 security_error!("requested_rem is not a subset of allowed");
805 security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
806 decision = false;
807 };
808
809 if !requested_pres_classes.is_subset(&pres_cls) {
810 security_error!("requested_pres_classes is not a subset of allowed");
811 security_error!(
812 "requested_classes: {:?} !⊆ allowed: {:?}",
813 requested_pres_classes,
814 pres_cls
815 );
816 decision = false;
817 };
818
819 if !requested_rem_classes.is_subset(&rem_cls) {
820 security_error!("requested_rem_classes is not a subset of allowed");
821 security_error!(
822 "requested_classes: {:?} !⊆ allowed: {:?}",
823 requested_rem_classes,
824 rem_cls
825 );
826 decision = false;
827 }
828
829 if decision {
830 debug!("passed pres, rem, classes check.");
831 }
832
833 decision
835 }
836 }
837 });
838
839 if r {
840 debug!("allowed modify of {} entries ✅", entries.len());
841 } else {
842 security_access!("denied ❌ - modifications may not proceed");
843 }
844 Ok(r)
845 }
846
847 #[instrument(level = "debug", name = "access::create_allow_operation", skip_all)]
848 fn create_allow_operation(
849 &self,
850 ce: &CreateEvent,
851 entries: &[Entry<EntryInit, EntryNew>],
852 ) -> Result<bool, OperationError> {
853 let create_state = self.get_create();
855 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
856
857 let ident_memberof = ce.ident.get_memberof();
858
859 let related_acp: Vec<_> = create_state
861 .iter()
862 .filter_map(|acs| {
863 let (receiver_condition, target_condition) = resolve_access_conditions(
864 &ce.ident,
865 ident_memberof,
866 &acs.acp.receiver,
867 &acs.acp.target,
868 acp_resolve_filter_cache,
869 )?;
870
871 Some(AccessControlCreateResolved {
872 acp: acs,
873 receiver_condition,
874 target_condition,
875 })
876 })
877 .collect();
878
879 let decision = entries.iter().all(|e| {
881 let requested_pres: BTreeSet<_> = e.attr_keys().cloned().collect();
882 let Some(requested_pres_classes) = e
883 .get_ava_as_iutf8(Attribute::Class)
884 .map(|set| set.iter().map(|s| s.as_str()).collect::<BTreeSet<_>>())
885 else {
886 error!("unable to perform access control checks on entry with no classes, denied.");
887 return false;
888 };
889
890 debug!(?requested_pres, "Requested present set");
891 debug!(?requested_pres_classes, "Requested present class set");
892 debug!(entry_id = %e.get_display_id());
893
894 match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
895 CreateResult::Deny => false,
896 CreateResult::Grant => true,
897 CreateResult::Allow { pres, pres_cls } => {
898 let mut decision = true;
899
900 if !requested_pres.is_subset(&pres) {
901 security_error!("requested_pres is not a subset of allowed");
902 security_error!(
903 "requested_pres: {:?} !⊆ allowed: {:?}",
904 requested_pres,
905 pres
906 );
907 decision = false
908 };
909
910 if !requested_pres_classes.is_subset(&pres_cls) {
911 security_error!("requested_pres_classes is not a subset of allowed");
912 security_error!(
913 "requested_classes: {:?} !⊆ allowed: {:?}",
914 requested_pres_classes,
915 pres_cls
916 );
917 decision = false;
918 };
919
920 if decision {
921 debug!("passed pres, classes check.");
922 }
923
924 decision
925 }
926 }
927 });
928
929 if decision {
930 debug!("allowed create of {} entries ✅", entries.len());
931 } else {
932 security_access!("denied ❌ - create may not proceed");
933 }
934
935 Ok(decision)
936 }
937
938 #[instrument(level = "trace", name = "access::delete_related_acp", skip_all)]
939 fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
940 let delete_state = self.get_delete();
942 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
943
944 let ident_memberof = ident.get_memberof();
945
946 let related_acp: Vec<_> = delete_state
947 .iter()
948 .filter_map(|acs| {
949 let (receiver_condition, target_condition) = resolve_access_conditions(
950 ident,
951 ident_memberof,
952 &acs.acp.receiver,
953 &acs.acp.target,
954 acp_resolve_filter_cache,
955 )?;
956
957 Some(AccessControlDeleteResolved {
958 acp: acs,
959 receiver_condition,
960 target_condition,
961 })
962 })
963 .collect();
964
965 related_acp
966 }
967
968 #[instrument(level = "debug", name = "access::delete_allow_operation", skip_all)]
969 fn delete_allow_operation(
970 &self,
971 de: &DeleteEvent,
972 entries: &[Arc<EntrySealedCommitted>],
973 ) -> Result<bool, OperationError> {
974 let related_acp = self.delete_related_acp(&de.ident);
976
977 let r = entries.iter().all(|e| {
979 match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
980 DeleteResult::Deny => false,
981 DeleteResult::Grant => true,
982 }
983 });
984 if r {
985 debug!("allowed delete of {} entries ✅", entries.len());
986 } else {
987 security_access!("denied ❌ - delete may not proceed");
988 }
989 Ok(r)
990 }
991
992 #[instrument(level = "debug", name = "access::effective_permission_check", skip_all)]
993 fn effective_permission_check(
994 &self,
995 ident: &Identity,
996 attrs: Option<BTreeSet<Attribute>>,
997 entries: &[Arc<EntrySealedCommitted>],
998 ) -> Result<Vec<AccessEffectivePermission>, OperationError> {
999 let ident_uuid = match &ident.origin {
1008 IdentType::Internal(_) => {
1009 security_critical!("IMPOSSIBLE STATE: Internal search in external interface?! Returning empty for safety.");
1012 return Err(OperationError::InvalidState);
1014 }
1015 IdentType::Synch(_) => {
1016 security_critical!("Blocking sync check");
1017 return Err(OperationError::InvalidState);
1018 }
1019 IdentType::User(u) => u.entry.get_uuid(),
1020 };
1021
1022 trace!(ident = %ident, "Effective permission check");
1023 let search_related_acp = self.search_related_acp(ident, attrs.as_ref());
1028 let modify_related_acp = self.modify_related_acp(ident);
1030 let delete_related_acp = self.delete_related_acp(ident);
1032
1033 let sync_agmts = self.get_sync_agreements();
1034
1035 let effective_permissions: Vec<_> = entries
1036 .iter()
1037 .map(|entry| {
1038 self.entry_effective_permission_check(
1039 ident,
1040 ident_uuid,
1041 entry,
1042 &search_related_acp,
1043 &modify_related_acp,
1044 &delete_related_acp,
1045 sync_agmts,
1046 )
1047 })
1048 .collect();
1049
1050 effective_permissions.iter().for_each(|ep| {
1051 trace!(?ep);
1052 });
1053
1054 Ok(effective_permissions)
1055 }
1056
1057 fn entry_effective_permission_check<'b>(
1058 &'b self,
1059 ident: &Identity,
1060 ident_uuid: Uuid,
1061 entry: &Arc<EntrySealedCommitted>,
1062 search_related_acp: &[AccessControlSearchResolved<'b>],
1063 modify_related_acp: &[AccessControlModifyResolved<'b>],
1064 delete_related_acp: &[AccessControlDeleteResolved<'b>],
1065 sync_agmts: &HashMap<Uuid, BTreeSet<Attribute>>,
1066 ) -> AccessEffectivePermission {
1067 let search_effective = match apply_search_access(ident, search_related_acp, entry) {
1069 SearchResult::Deny => Access::Deny,
1070 SearchResult::Grant => Access::Grant,
1071 SearchResult::Allow(allowed_attrs) => {
1072 Access::Allow(allowed_attrs.into_iter().collect())
1074 }
1075 };
1076
1077 let (modify_pres, modify_rem, modify_pres_class, modify_rem_class) =
1079 match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
1080 ModifyResult::Deny => (
1081 Access::Deny,
1082 Access::Deny,
1083 AccessClass::Deny,
1084 AccessClass::Deny,
1085 ),
1086 ModifyResult::Grant => (
1087 Access::Grant,
1088 Access::Grant,
1089 AccessClass::Grant,
1090 AccessClass::Grant,
1091 ),
1092 ModifyResult::Allow {
1093 pres,
1094 rem,
1095 pres_cls,
1096 rem_cls,
1097 } => (
1098 Access::Allow(pres.into_iter().collect()),
1099 Access::Allow(rem.into_iter().collect()),
1100 AccessClass::Allow(pres_cls.into_iter().map(|s| s.into()).collect()),
1101 AccessClass::Allow(rem_cls.into_iter().map(|s| s.into()).collect()),
1102 ),
1103 };
1104
1105 let delete_status = apply_delete_access(ident, delete_related_acp, entry);
1107
1108 let delete = match delete_status {
1109 DeleteResult::Deny => false,
1110 DeleteResult::Grant => true,
1111 };
1112
1113 AccessEffectivePermission {
1114 ident: ident_uuid,
1115 target: entry.get_uuid(),
1116 delete,
1117 search: search_effective,
1118 modify_pres,
1119 modify_rem,
1120 modify_pres_class,
1121 modify_rem_class,
1122 }
1123 }
1124}
1125
1126pub struct AccessControlsWriteTransaction<'a> {
1127 inner: CowCellWriteTxn<'a, AccessControlsInner>,
1128 acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
1129}
1130
1131impl AccessControlsWriteTransaction<'_> {
1132 pub fn update_search(
1136 &mut self,
1137 mut acps: Vec<AccessControlSearch>,
1138 ) -> Result<(), OperationError> {
1139 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_search);
1140 Ok(())
1141 }
1142
1143 pub fn update_create(
1144 &mut self,
1145 mut acps: Vec<AccessControlCreate>,
1146 ) -> Result<(), OperationError> {
1147 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_create);
1148 Ok(())
1149 }
1150
1151 pub fn update_modify(
1152 &mut self,
1153 mut acps: Vec<AccessControlModify>,
1154 ) -> Result<(), OperationError> {
1155 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_modify);
1156 Ok(())
1157 }
1158
1159 pub fn update_delete(
1160 &mut self,
1161 mut acps: Vec<AccessControlDelete>,
1162 ) -> Result<(), OperationError> {
1163 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_delete);
1164 Ok(())
1165 }
1166
1167 pub fn update_sync_agreements(
1168 &mut self,
1169 mut sync_agreements: HashMap<Uuid, BTreeSet<Attribute>>,
1170 ) {
1171 std::mem::swap(
1172 &mut sync_agreements,
1173 &mut self.inner.deref_mut().sync_agreements,
1174 );
1175 }
1176
1177 pub fn commit(self) -> Result<(), OperationError> {
1178 self.inner.commit();
1179
1180 Ok(())
1181 }
1182}
1183
1184impl<'a> AccessControlsTransaction<'a> for AccessControlsWriteTransaction<'a> {
1185 fn get_search(&self) -> &Vec<AccessControlSearch> {
1186 &self.inner.acps_search
1187 }
1188
1189 fn get_create(&self) -> &Vec<AccessControlCreate> {
1190 &self.inner.acps_create
1191 }
1192
1193 fn get_modify(&self) -> &Vec<AccessControlModify> {
1194 &self.inner.acps_modify
1195 }
1196
1197 fn get_delete(&self) -> &Vec<AccessControlDelete> {
1198 &self.inner.acps_delete
1199 }
1200
1201 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
1202 &self.inner.sync_agreements
1203 }
1204
1205 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
1206 unsafe {
1207 let mptr = self.acp_resolve_filter_cache.as_ptr();
1208 &mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
1209 }
1210 }
1211}
1212
1213pub struct AccessControlsReadTransaction<'a> {
1218 inner: CowCellReadTxn<AccessControlsInner>,
1219 acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
1221}
1222
1223unsafe impl Sync for AccessControlsReadTransaction<'_> {}
1224
1225unsafe impl Send for AccessControlsReadTransaction<'_> {}
1226
1227impl<'a> AccessControlsTransaction<'a> for AccessControlsReadTransaction<'a> {
1228 fn get_search(&self) -> &Vec<AccessControlSearch> {
1229 &self.inner.acps_search
1230 }
1231
1232 fn get_create(&self) -> &Vec<AccessControlCreate> {
1233 &self.inner.acps_create
1234 }
1235
1236 fn get_modify(&self) -> &Vec<AccessControlModify> {
1237 &self.inner.acps_modify
1238 }
1239
1240 fn get_delete(&self) -> &Vec<AccessControlDelete> {
1241 &self.inner.acps_delete
1242 }
1243
1244 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
1245 &self.inner.sync_agreements
1246 }
1247
1248 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
1249 unsafe {
1250 let mptr = self.acp_resolve_filter_cache.as_ptr();
1251 &mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
1252 }
1253 }
1254}
1255
1256impl Default for AccessControls {
1261 #![allow(clippy::expect_used)]
1262 fn default() -> Self {
1263 AccessControls {
1264 inner: CowCell::new(AccessControlsInner {
1265 acps_search: Vec::with_capacity(0),
1266 acps_create: Vec::with_capacity(0),
1267 acps_modify: Vec::with_capacity(0),
1268 acps_delete: Vec::with_capacity(0),
1269 sync_agreements: HashMap::default(),
1270 }),
1271 acp_resolve_filter_cache: ARCacheBuilder::new()
1274 .set_size(ACP_RESOLVE_FILTER_CACHE_MAX, ACP_RESOLVE_FILTER_CACHE_LOCAL)
1275 .set_reader_quiesce(true)
1276 .build()
1277 .expect("Failed to construct acp_resolve_filter_cache"),
1278 }
1279 }
1280}
1281
1282impl AccessControls {
1283 pub fn try_quiesce(&self) {
1284 self.acp_resolve_filter_cache.try_quiesce();
1285 }
1286
1287 pub fn read(&self) -> AccessControlsReadTransaction<'_> {
1288 AccessControlsReadTransaction {
1289 inner: self.inner.read(),
1290 acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
1292 }
1293 }
1294
1295 pub fn write(&self) -> AccessControlsWriteTransaction<'_> {
1296 AccessControlsWriteTransaction {
1297 inner: self.inner.write(),
1298 acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
1301 }
1302 }
1303}
1304
1305#[cfg(test)]
1306mod tests {
1307 use hashbrown::HashMap;
1308 use std::collections::BTreeSet;
1309 use std::sync::Arc;
1310
1311 use uuid::uuid;
1312
1313 use super::{
1314 profiles::{
1315 AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlProfile,
1316 AccessControlSearch, AccessControlTarget,
1317 },
1318 Access, AccessClass, AccessControls, AccessControlsTransaction, AccessEffectivePermission,
1319 };
1320 use crate::migration_data::BUILTIN_ACCOUNT_ANONYMOUS;
1321 use crate::prelude::*;
1322 use crate::valueset::ValueSetIname;
1323
1324 const UUID_TEST_ACCOUNT_1: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
1325 const UUID_TEST_ACCOUNT_2: Uuid = uuid::uuid!("cec0852a-abdf-4ea6-9dae-d3157cb33d3a");
1326 const UUID_TEST_GROUP_1: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
1327 const UUID_TEST_GROUP_2: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
1328
1329 pub static E_TEST_ACCOUNT_1: LazyLock<Arc<EntrySealedCommitted>> = LazyLock::new(|| {
1330 Arc::new(
1331 entry_init!(
1332 (Attribute::Class, EntryClass::Object.to_value()),
1333 (Attribute::Name, Value::new_iname("test_account_1")),
1334 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
1335 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1))
1336 )
1337 .into_sealed_committed(),
1338 )
1339 });
1340 pub static E_TEST_ACCOUNT_2: LazyLock<Arc<EntrySealedCommitted>> = LazyLock::new(|| {
1341 Arc::new(
1342 entry_init!(
1343 (Attribute::Class, EntryClass::Object.to_value()),
1344 (Attribute::Name, Value::new_iname("test_account_1")),
1345 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_2)),
1346 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_2))
1347 )
1348 .into_sealed_committed(),
1349 )
1350 });
1351
1352 macro_rules! acp_from_entry_err {
1353 (
1354 $qs:expr,
1355 $e:expr,
1356 $type:ty
1357 ) => {{
1358 let ev1 = $e.into_sealed_committed();
1359
1360 let r1 = <$type>::try_from($qs, &ev1);
1361 error!(?r1);
1362 assert!(r1.is_err());
1363 }};
1364 }
1365
1366 macro_rules! acp_from_entry_ok {
1367 (
1368 $qs:expr,
1369 $e:expr,
1370 $type:ty
1371 ) => {{
1372 let ev1 = $e.into_sealed_committed();
1373
1374 let r1 = <$type>::try_from($qs, &ev1);
1375 assert!(r1.is_ok());
1376 r1.unwrap()
1377 }};
1378 }
1379
1380 #[qs_test]
1381 async fn test_access_acp_parser(qs: &QueryServer) {
1382 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1391
1392 acp_from_entry_err!(
1393 &mut qs_write,
1394 entry_init!(
1395 (Attribute::Class, EntryClass::Object.to_value()),
1396 (Attribute::Name, Value::new_iname("acp_invalid")),
1397 (
1398 Attribute::Uuid,
1399 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1400 )
1401 ),
1402 AccessControlProfile
1403 );
1404
1405 acp_from_entry_err!(
1406 &mut qs_write,
1407 entry_init!(
1408 (Attribute::Class, EntryClass::Object.to_value()),
1409 (
1410 Attribute::Class,
1411 EntryClass::AccessControlProfile.to_value()
1412 ),
1413 (
1414 Attribute::Class,
1415 EntryClass::AccessControlReceiverGroup.to_value()
1416 ),
1417 (
1418 Attribute::Class,
1419 EntryClass::AccessControlTargetScope.to_value()
1420 ),
1421 (Attribute::Name, Value::new_iname("acp_invalid")),
1422 (
1423 Attribute::Uuid,
1424 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1425 )
1426 ),
1427 AccessControlProfile
1428 );
1429
1430 acp_from_entry_err!(
1431 &mut qs_write,
1432 entry_init!(
1433 (Attribute::Class, EntryClass::Object.to_value()),
1434 (
1435 Attribute::Class,
1436 EntryClass::AccessControlProfile.to_value()
1437 ),
1438 (
1439 Attribute::Class,
1440 EntryClass::AccessControlReceiverGroup.to_value()
1441 ),
1442 (
1443 Attribute::Class,
1444 EntryClass::AccessControlTargetScope.to_value()
1445 ),
1446 (Attribute::Name, Value::new_iname("acp_invalid")),
1447 (
1448 Attribute::Uuid,
1449 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1450 ),
1451 (Attribute::AcpReceiverGroup, Value::Bool(true)),
1452 (Attribute::AcpTargetScope, Value::Bool(true))
1453 ),
1454 AccessControlProfile
1455 );
1456
1457 acp_from_entry_ok!(
1459 &mut qs_write,
1460 entry_init!(
1461 (Attribute::Class, EntryClass::Object.to_value()),
1462 (
1463 Attribute::Class,
1464 EntryClass::AccessControlProfile.to_value()
1465 ),
1466 (
1467 Attribute::Class,
1468 EntryClass::AccessControlReceiverGroup.to_value()
1469 ),
1470 (
1471 Attribute::Class,
1472 EntryClass::AccessControlTargetScope.to_value()
1473 ),
1474 (Attribute::Name, Value::new_iname("acp_valid")),
1475 (
1476 Attribute::Uuid,
1477 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1478 ),
1479 (
1480 Attribute::AcpReceiverGroup,
1481 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1482 ),
1483 (
1484 Attribute::AcpTargetScope,
1485 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1486 )
1487 ),
1488 AccessControlProfile
1489 );
1490 }
1491
1492 #[qs_test]
1493 async fn test_access_acp_delete_parser(qs: &QueryServer) {
1494 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1495
1496 acp_from_entry_err!(
1497 &mut qs_write,
1498 entry_init!(
1499 (Attribute::Class, EntryClass::Object.to_value()),
1500 (
1501 Attribute::Class,
1502 EntryClass::AccessControlProfile.to_value()
1503 ),
1504 (Attribute::Name, Value::new_iname("acp_valid")),
1505 (
1506 Attribute::Uuid,
1507 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1508 ),
1509 (
1510 Attribute::AcpReceiverGroup,
1511 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1512 ),
1513 (
1514 Attribute::AcpTargetScope,
1515 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1516 )
1517 ),
1518 AccessControlDelete
1519 );
1520
1521 acp_from_entry_ok!(
1522 &mut qs_write,
1523 entry_init!(
1524 (Attribute::Class, EntryClass::Object.to_value()),
1525 (
1526 Attribute::Class,
1527 EntryClass::AccessControlProfile.to_value()
1528 ),
1529 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
1530 (Attribute::Name, Value::new_iname("acp_valid")),
1531 (
1532 Attribute::Uuid,
1533 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1534 ),
1535 (
1536 Attribute::AcpReceiverGroup,
1537 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1538 ),
1539 (
1540 Attribute::AcpTargetScope,
1541 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1542 )
1543 ),
1544 AccessControlDelete
1545 );
1546 }
1547
1548 #[qs_test]
1549 async fn test_access_acp_search_parser(qs: &QueryServer) {
1550 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1552
1553 acp_from_entry_err!(
1555 &mut qs_write,
1556 entry_init!(
1557 (Attribute::Class, EntryClass::Object.to_value()),
1558 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1559 (Attribute::Name, Value::new_iname("acp_valid")),
1560 (
1561 Attribute::Uuid,
1562 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1563 ),
1564 (
1565 Attribute::AcpReceiverGroup,
1566 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1567 ),
1568 (
1569 Attribute::AcpTargetScope,
1570 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1571 ),
1572 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1573 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1574 ),
1575 AccessControlSearch
1576 );
1577
1578 acp_from_entry_err!(
1580 &mut qs_write,
1581 entry_init!(
1582 (Attribute::Class, EntryClass::Object.to_value()),
1583 (
1584 Attribute::Class,
1585 EntryClass::AccessControlProfile.to_value()
1586 ),
1587 (Attribute::Name, Value::new_iname("acp_valid")),
1588 (
1589 Attribute::Uuid,
1590 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1591 ),
1592 (
1593 Attribute::AcpReceiverGroup,
1594 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1595 ),
1596 (
1597 Attribute::AcpTargetScope,
1598 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1599 ),
1600 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1601 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1602 ),
1603 AccessControlSearch
1604 );
1605
1606 acp_from_entry_err!(
1608 &mut qs_write,
1609 entry_init!(
1610 (Attribute::Class, EntryClass::Object.to_value()),
1611 (
1612 Attribute::Class,
1613 EntryClass::AccessControlProfile.to_value()
1614 ),
1615 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1616 (Attribute::Name, Value::new_iname("acp_valid")),
1617 (
1618 Attribute::Uuid,
1619 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1620 ),
1621 (
1622 Attribute::AcpReceiverGroup,
1623 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1624 ),
1625 (
1626 Attribute::AcpTargetScope,
1627 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1628 )
1629 ),
1630 AccessControlSearch
1631 );
1632
1633 acp_from_entry_ok!(
1635 &mut qs_write,
1636 entry_init!(
1637 (Attribute::Class, EntryClass::Object.to_value()),
1638 (
1639 Attribute::Class,
1640 EntryClass::AccessControlProfile.to_value()
1641 ),
1642 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1643 (Attribute::Name, Value::new_iname("acp_valid")),
1644 (
1645 Attribute::Uuid,
1646 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1647 ),
1648 (
1649 Attribute::AcpReceiverGroup,
1650 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1651 ),
1652 (
1653 Attribute::AcpTargetScope,
1654 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1655 ),
1656 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1657 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1658 ),
1659 AccessControlSearch
1660 );
1661 }
1662
1663 #[qs_test]
1664 async fn test_access_acp_modify_parser(qs: &QueryServer) {
1665 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1667
1668 acp_from_entry_err!(
1669 &mut qs_write,
1670 entry_init!(
1671 (Attribute::Class, EntryClass::Object.to_value()),
1672 (
1673 Attribute::Class,
1674 EntryClass::AccessControlProfile.to_value()
1675 ),
1676 (Attribute::Name, Value::new_iname("acp_invalid")),
1677 (
1678 Attribute::Uuid,
1679 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1680 ),
1681 (
1682 Attribute::AcpReceiverGroup,
1683 Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1684 ),
1685 (
1686 Attribute::AcpTargetScope,
1687 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1688 )
1689 ),
1690 AccessControlModify
1691 );
1692
1693 acp_from_entry_ok!(
1694 &mut qs_write,
1695 entry_init!(
1696 (Attribute::Class, EntryClass::Object.to_value()),
1697 (
1698 Attribute::Class,
1699 EntryClass::AccessControlProfile.to_value()
1700 ),
1701 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1702 (Attribute::Name, Value::new_iname("acp_valid")),
1703 (
1704 Attribute::Uuid,
1705 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1706 ),
1707 (
1708 Attribute::AcpReceiverGroup,
1709 Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1710 ),
1711 (
1712 Attribute::AcpTargetScope,
1713 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1714 )
1715 ),
1716 AccessControlModify
1717 );
1718
1719 acp_from_entry_ok!(
1720 &mut qs_write,
1721 entry_init!(
1722 (Attribute::Class, EntryClass::Object.to_value()),
1723 (
1724 Attribute::Class,
1725 EntryClass::AccessControlProfile.to_value()
1726 ),
1727 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1728 (Attribute::Name, Value::new_iname("acp_valid")),
1729 (
1730 Attribute::Uuid,
1731 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1732 ),
1733 (
1734 Attribute::AcpReceiverGroup,
1735 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1736 ),
1737 (
1738 Attribute::AcpTargetScope,
1739 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1740 ),
1741 (
1742 Attribute::AcpModifyRemovedAttr,
1743 Value::from(Attribute::Name)
1744 ),
1745 (
1746 Attribute::AcpModifyPresentAttr,
1747 Value::from(Attribute::Name)
1748 ),
1749 (Attribute::AcpModifyClass, EntryClass::Object.to_value())
1750 ),
1751 AccessControlModify
1752 );
1753 }
1754
1755 #[qs_test]
1756 async fn test_access_acp_create_parser(qs: &QueryServer) {
1757 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1759
1760 acp_from_entry_err!(
1761 &mut qs_write,
1762 entry_init!(
1763 (Attribute::Class, EntryClass::Object.to_value()),
1764 (
1765 Attribute::Class,
1766 EntryClass::AccessControlProfile.to_value()
1767 ),
1768 (Attribute::Name, Value::new_iname("acp_invalid")),
1769 (
1770 Attribute::Uuid,
1771 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1772 ),
1773 (
1774 Attribute::AcpReceiverGroup,
1775 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1776 ),
1777 (
1778 Attribute::AcpTargetScope,
1779 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1780 ),
1781 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1782 (Attribute::AcpCreateClass, EntryClass::Object.to_value())
1783 ),
1784 AccessControlCreate
1785 );
1786
1787 acp_from_entry_ok!(
1788 &mut qs_write,
1789 entry_init!(
1790 (Attribute::Class, EntryClass::Object.to_value()),
1791 (
1792 Attribute::Class,
1793 EntryClass::AccessControlProfile.to_value()
1794 ),
1795 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1796 (Attribute::Name, Value::new_iname("acp_valid")),
1797 (
1798 Attribute::Uuid,
1799 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1800 ),
1801 (
1802 Attribute::AcpReceiverGroup,
1803 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1804 ),
1805 (
1806 Attribute::AcpTargetScope,
1807 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1808 )
1809 ),
1810 AccessControlCreate
1811 );
1812
1813 acp_from_entry_ok!(
1814 &mut qs_write,
1815 entry_init!(
1816 (Attribute::Class, EntryClass::Object.to_value()),
1817 (
1818 Attribute::Class,
1819 EntryClass::AccessControlProfile.to_value()
1820 ),
1821 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1822 (Attribute::Name, Value::new_iname("acp_valid")),
1823 (
1824 Attribute::Uuid,
1825 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1826 ),
1827 (
1828 Attribute::AcpReceiverGroup,
1829 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1830 ),
1831 (
1832 Attribute::AcpTargetScope,
1833 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1834 ),
1835 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1836 (Attribute::AcpCreateClass, EntryClass::Object.to_value())
1837 ),
1838 AccessControlCreate
1839 );
1840 }
1841
1842 #[qs_test]
1843 async fn test_access_acp_compound_parser(qs: &QueryServer) {
1844 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1849
1850 let e = entry_init!(
1851 (Attribute::Class, EntryClass::Object.to_value()),
1852 (
1853 Attribute::Class,
1854 EntryClass::AccessControlProfile.to_value()
1855 ),
1856 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1857 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
1858 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1859 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1860 (Attribute::Name, Value::new_iname("acp_valid")),
1861 (
1862 Attribute::Uuid,
1863 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1864 ),
1865 (
1866 Attribute::AcpReceiverGroup,
1867 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1868 ),
1869 (
1870 Attribute::AcpTargetScope,
1871 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1872 ),
1873 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1874 (Attribute::AcpCreateClass, EntryClass::Class.to_value()),
1875 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1876 (
1877 Attribute::AcpModifyRemovedAttr,
1878 Value::from(Attribute::Name)
1879 ),
1880 (
1881 Attribute::AcpModifyPresentAttr,
1882 Value::from(Attribute::Name)
1883 ),
1884 (Attribute::AcpModifyClass, EntryClass::Object.to_value())
1885 );
1886
1887 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlCreate);
1888 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlDelete);
1889 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlModify);
1890 acp_from_entry_ok!(&mut qs_write, e, AccessControlSearch);
1891 }
1892
1893 macro_rules! test_acp_search {
1894 (
1895 $se:expr,
1896 $controls:expr,
1897 $entries:expr,
1898 $expect:expr
1899 ) => {{
1900 let ac = AccessControls::default();
1901 let mut acw = ac.write();
1902 acw.update_search($controls).expect("Failed to update");
1903 let acw = acw;
1904
1905 let res = acw
1906 .search_filter_entries(&mut $se, $entries)
1907 .expect("op failed");
1908 debug!("result --> {:?}", res);
1909 debug!("expect --> {:?}", $expect);
1910 assert_eq!(res, $expect);
1912 }};
1913 }
1914
1915 macro_rules! test_acp_search_reduce {
1916 (
1917 $se:expr,
1918 $controls:expr,
1919 $entries:expr,
1920 $expect:expr
1921 ) => {{
1922 let ac = AccessControls::default();
1923 let mut acw = ac.write();
1924 acw.update_search($controls).expect("Failed to update");
1925 let acw = acw;
1926
1927 let res = acw
1929 .search_filter_entries(&mut $se, $entries)
1930 .expect("operation failed");
1931 let reduced = acw
1933 .search_filter_entry_attributes(&mut $se, res)
1934 .expect("operation failed");
1935
1936 let expect_set: Vec<Entry<EntryReduced, EntryCommitted>> =
1938 $expect.into_iter().map(|e| e.into_reduced()).collect();
1939
1940 debug!("expect --> {:?}", expect_set);
1941 debug!("result --> {:?}", reduced);
1942 assert_eq!(reduced, expect_set);
1944 }};
1945 }
1946
1947 #[test]
1948 fn test_access_internal_search() {
1949 let se = SearchEvent::new_internal_invalid(filter!(f_pres(Attribute::Class)));
1951
1952 let expect = vec![E_TEST_ACCOUNT_1.clone()];
1953 let entries = vec![E_TEST_ACCOUNT_1.clone()];
1954
1955 test_acp_search!(
1957 &se,
1958 vec![AccessControlSearch::from_raw(
1959 "test_acp",
1960 Uuid::new_v4(),
1961 UUID_TEST_GROUP_1,
1962 filter_valid!(f_pres(Attribute::NonExist)), Attribute::Name.as_ref(), )],
1965 entries,
1966 expect
1967 );
1968 }
1969
1970 #[test]
1971 fn test_access_enforce_search() {
1972 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
1974 let ev2 = E_TESTPERSON_2.clone().into_sealed_committed();
1975
1976 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
1977
1978 let se_a = SearchEvent::new_impersonate_entry(
1979 E_TEST_ACCOUNT_1.clone(),
1980 filter_all!(f_pres(Attribute::Name)),
1981 );
1982 let ex_a = vec![Arc::new(ev1)];
1983
1984 let se_b = SearchEvent::new_impersonate_entry(
1985 E_TEST_ACCOUNT_2.clone(),
1986 filter_all!(f_pres(Attribute::Name)),
1987 );
1988 let ex_b = vec![];
1989
1990 let acp = AccessControlSearch::from_raw(
1991 "test_acp",
1992 Uuid::new_v4(),
1993 UUID_TEST_GROUP_1,
1995 filter_valid!(f_eq(
1997 Attribute::Name,
1998 PartialValue::new_iname("testperson1")
1999 )),
2000 Attribute::Name.as_ref(),
2003 );
2004
2005 test_acp_search!(&se_a, vec![acp.clone()], r_set.clone(), ex_a);
2007
2008 test_acp_search!(&se_b, vec![acp], r_set, ex_b);
2010 }
2011
2012 #[test]
2013 fn test_access_enforce_scope_search() {
2014 sketching::test_init();
2015 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2017
2018 let ex_some = vec![Arc::new(ev1.clone())];
2019
2020 let r_set = vec![Arc::new(ev1)];
2021
2022 let se_ro = SearchEvent::new_impersonate_identity(
2023 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2024 filter_all!(f_pres(Attribute::Name)),
2025 );
2026
2027 let se_rw = SearchEvent::new_impersonate_identity(
2028 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2029 filter_all!(f_pres(Attribute::Name)),
2030 );
2031
2032 let acp = AccessControlSearch::from_raw(
2033 "test_acp",
2034 Uuid::new_v4(),
2035 UUID_TEST_GROUP_1,
2037 filter_valid!(f_eq(
2039 Attribute::Name,
2040 PartialValue::new_iname("testperson1")
2041 )),
2042 Attribute::Name.as_ref(),
2045 );
2046
2047 test_acp_search!(&se_ro, vec![acp.clone()], r_set.clone(), ex_some);
2049
2050 test_acp_search!(&se_rw, vec![acp], r_set, ex_some);
2051 }
2052
2053 #[test]
2054 fn test_access_enforce_scope_search_attrs() {
2055 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2059 let r_set = vec![Arc::new(ev1)];
2060
2061 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2062
2063 let ex_anon_some = vec![exv1];
2064
2065 let se_anon_ro = SearchEvent::new_impersonate_identity(
2066 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2067 filter_all!(f_pres(Attribute::Name)),
2068 );
2069
2070 let acp = AccessControlSearch::from_raw(
2071 "test_acp",
2072 Uuid::new_v4(),
2073 UUID_TEST_GROUP_1,
2075 filter_valid!(f_eq(
2077 Attribute::Name,
2078 PartialValue::new_iname("testperson1")
2079 )),
2080 Attribute::Name.as_ref(),
2083 );
2084
2085 test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
2087 }
2088
2089 pub static E_TESTPERSON_1_REDUCED: LazyLock<EntryInitNew> =
2090 LazyLock::new(|| entry_init_fn([(Attribute::Name, Value::new_iname("testperson1"))]));
2091
2092 #[test]
2093 fn test_access_enforce_search_attrs() {
2094 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2098 let r_set = vec![Arc::new(ev1)];
2099
2100 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2101 let ex_anon = vec![exv1];
2102
2103 let se_anon = SearchEvent::new_impersonate_entry(
2104 E_TEST_ACCOUNT_1.clone(),
2105 filter_all!(f_eq(
2106 Attribute::Name,
2107 PartialValue::new_iname("testperson1")
2108 )),
2109 );
2110
2111 let acp = AccessControlSearch::from_raw(
2112 "test_acp",
2113 Uuid::new_v4(),
2114 UUID_TEST_GROUP_1,
2116 filter_valid!(f_eq(
2118 Attribute::Name,
2119 PartialValue::new_iname("testperson1")
2120 )),
2121 Attribute::Name.as_ref(),
2124 );
2125
2126 test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
2128 }
2129
2130 #[test]
2131 fn test_access_enforce_search_attrs_req() {
2132 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2136
2137 let r_set = vec![Arc::new(ev1)];
2138
2139 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2140 let ex_anon = vec![exv1];
2141
2142 let mut se_anon = SearchEvent::new_impersonate_entry(
2143 E_TEST_ACCOUNT_1.clone(),
2144 filter_all!(f_eq(
2145 Attribute::Name,
2146 PartialValue::new_iname("testperson1")
2147 )),
2148 );
2149 se_anon.attrs = Some(btreeset![Attribute::Name]);
2151
2152 let acp = AccessControlSearch::from_raw(
2153 "test_acp",
2154 Uuid::new_v4(),
2155 UUID_TEST_GROUP_1,
2157 filter_valid!(f_eq(
2159 Attribute::Name,
2160 PartialValue::new_iname("testperson1")
2161 )),
2162 "name uuid",
2165 );
2166
2167 test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
2169 }
2170
2171 macro_rules! test_acp_modify {
2172 (
2173 $me:expr,
2174 $controls:expr,
2175 $entries:expr,
2176 $expect:expr
2177 ) => {{
2178 let ac = AccessControls::default();
2179 let mut acw = ac.write();
2180 acw.update_modify($controls).expect("Failed to update");
2181 let acw = acw;
2182
2183 let res = acw
2184 .modify_allow_operation(&mut $me, $entries)
2185 .expect("op failed");
2186
2187 debug!("result --> {:?}", res);
2188 debug!("expect --> {:?}", $expect);
2189 assert_eq!($expect, res);
2191 }};
2192 (
2193 $me:expr,
2194 $controls:expr,
2195 $sync_uuid:expr,
2196 $sync_yield_attr:expr,
2197 $entries:expr,
2198 $expect:expr
2199 ) => {{
2200 let ac = AccessControls::default();
2201 let mut acw = ac.write();
2202 acw.update_modify($controls).expect("Failed to update");
2203 let mut sync_agmt = HashMap::new();
2204 let mut set = BTreeSet::new();
2205 set.insert($sync_yield_attr);
2206 sync_agmt.insert($sync_uuid, set);
2207 acw.update_sync_agreements(sync_agmt);
2208 let acw = acw;
2209
2210 let res = acw
2211 .modify_allow_operation(&mut $me, $entries)
2212 .expect("op failed");
2213
2214 debug!("result --> {:?}", res);
2215 debug!("expect --> {:?}", $expect);
2216 assert_eq!($expect, res);
2218 }};
2219 }
2220
2221 #[test]
2222 fn test_access_enforce_modify() {
2223 sketching::test_init();
2224
2225 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2226 let r_set = vec![Arc::new(ev1)];
2227
2228 let me_pres = ModifyEvent::new_impersonate_entry(
2230 E_TEST_ACCOUNT_1.clone(),
2231 filter_all!(f_eq(
2232 Attribute::Name,
2233 PartialValue::new_iname("testperson1")
2234 )),
2235 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2236 );
2237 let me_rem = ModifyEvent::new_impersonate_entry(
2239 E_TEST_ACCOUNT_1.clone(),
2240 filter_all!(f_eq(
2241 Attribute::Name,
2242 PartialValue::new_iname("testperson1")
2243 )),
2244 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
2245 );
2246 let me_purge = ModifyEvent::new_impersonate_entry(
2248 E_TEST_ACCOUNT_1.clone(),
2249 filter_all!(f_eq(
2250 Attribute::Name,
2251 PartialValue::new_iname("testperson1")
2252 )),
2253 modlist!([m_purge(Attribute::Name)]),
2254 );
2255
2256 let me_set = ModifyEvent::new_impersonate_entry(
2258 E_TEST_ACCOUNT_1.clone(),
2259 filter_all!(f_eq(
2260 Attribute::Name,
2261 PartialValue::new_iname("testperson1")
2262 )),
2263 modlist!([Modify::Set(Attribute::Name, ValueSetIname::new("value"))]),
2264 );
2265
2266 let me_pres_class = ModifyEvent::new_impersonate_entry(
2268 E_TEST_ACCOUNT_1.clone(),
2269 filter_all!(f_eq(
2270 Attribute::Name,
2271 PartialValue::new_iname("testperson1")
2272 )),
2273 modlist!([m_pres(Attribute::Class, &EntryClass::Account.to_value())]),
2274 );
2275 let me_rem_class = ModifyEvent::new_impersonate_entry(
2277 E_TEST_ACCOUNT_1.clone(),
2278 filter_all!(f_eq(
2279 Attribute::Name,
2280 PartialValue::new_iname("testperson1")
2281 )),
2282 modlist!([m_remove(
2283 Attribute::Class,
2284 &EntryClass::Account.to_partialvalue()
2285 )]),
2286 );
2287 let me_purge_class = ModifyEvent::new_impersonate_entry(
2289 E_TEST_ACCOUNT_1.clone(),
2290 filter_all!(f_eq(
2291 Attribute::Name,
2292 PartialValue::new_iname("testperson1")
2293 )),
2294 modlist!([m_purge(Attribute::Class)]),
2295 );
2296
2297 let me_set_class = ModifyEvent::new_impersonate_entry(
2299 E_TEST_ACCOUNT_1.clone(),
2300 filter_all!(f_eq(
2301 Attribute::Name,
2302 PartialValue::new_iname("testperson1")
2303 )),
2304 modlist!([Modify::Set(
2305 Attribute::Class,
2306 EntryClass::Account.to_valueset()
2307 )]),
2308 );
2309
2310 let acp_allow = AccessControlModify::from_raw(
2312 "test_modify_allow",
2313 Uuid::new_v4(),
2314 UUID_TEST_GROUP_1,
2316 filter_valid!(f_eq(
2318 Attribute::Name,
2319 PartialValue::new_iname("testperson1")
2320 )),
2321 "name class",
2323 "name class",
2325 EntryClass::Account.into(),
2327 EntryClass::Account.into(),
2329 );
2330 let acp_deny = AccessControlModify::from_raw(
2332 "test_modify_deny",
2333 Uuid::new_v4(),
2334 UUID_TEST_GROUP_1,
2336 filter_valid!(f_eq(
2338 Attribute::Name,
2339 PartialValue::new_iname("testperson1")
2340 )),
2341 "member class",
2343 "member class",
2345 EntryClass::Group.into(),
2346 EntryClass::Group.into(),
2347 );
2348 let acp_no_class = AccessControlModify::from_raw(
2350 "test_modify_no_class",
2351 Uuid::new_v4(),
2352 UUID_TEST_GROUP_1,
2354 filter_valid!(f_eq(
2356 Attribute::Name,
2357 PartialValue::new_iname("testperson1")
2358 )),
2359 "name class",
2361 "name class",
2363 EntryClass::Group.into(),
2365 EntryClass::Group.into(),
2366 );
2367
2368 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r_set, true);
2370 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r_set, true);
2372 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r_set, true);
2374 test_acp_modify!(&me_set, vec![acp_allow.clone()], &r_set, true);
2376
2377 test_acp_modify!(&me_pres, vec![acp_deny.clone()], &r_set, false);
2379 test_acp_modify!(&me_rem, vec![acp_deny.clone()], &r_set, false);
2381 test_acp_modify!(&me_purge, vec![acp_deny.clone()], &r_set, false);
2383 test_acp_modify!(&me_set, vec![acp_deny.clone()], &r_set, false);
2385
2386 test_acp_modify!(&me_pres_class, vec![acp_allow.clone()], &r_set, true);
2388 test_acp_modify!(&me_rem_class, vec![acp_allow.clone()], &r_set, true);
2390 test_acp_modify!(&me_purge_class, vec![acp_allow.clone()], &r_set, false);
2392 test_acp_modify!(&me_set_class, vec![acp_allow], &r_set, true);
2394
2395 test_acp_modify!(&me_pres_class, vec![acp_no_class.clone()], &r_set, false);
2397 test_acp_modify!(&me_pres_class, vec![acp_deny.clone()], &r_set, false);
2399 test_acp_modify!(&me_rem_class, vec![acp_no_class.clone()], &r_set, false);
2401 test_acp_modify!(&me_rem_class, vec![acp_deny.clone()], &r_set, false);
2403
2404 test_acp_modify!(&me_set_class, vec![acp_no_class], &r_set, false);
2406 test_acp_modify!(&me_set_class, vec![acp_deny], &r_set, false);
2408 }
2409
2410 #[test]
2411 fn test_access_enforce_scope_modify() {
2412 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2413 let r_set = vec![Arc::new(ev1)];
2414
2415 let me_pres_ro = ModifyEvent::new_impersonate_identity(
2417 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2418 filter_all!(f_eq(
2419 Attribute::Name,
2420 PartialValue::new_iname("testperson1")
2421 )),
2422 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2423 );
2424
2425 let me_pres_rw = ModifyEvent::new_impersonate_identity(
2427 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2428 filter_all!(f_eq(
2429 Attribute::Name,
2430 PartialValue::new_iname("testperson1")
2431 )),
2432 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2433 );
2434
2435 let acp_allow = AccessControlModify::from_raw(
2436 "test_modify_allow",
2437 Uuid::new_v4(),
2438 UUID_TEST_GROUP_1,
2440 filter_valid!(f_eq(
2442 Attribute::Name,
2443 PartialValue::new_iname("testperson1")
2444 )),
2445 "name class",
2447 "name class",
2449 EntryClass::Account.into(),
2451 EntryClass::Account.into(),
2452 );
2453
2454 test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
2455
2456 test_acp_modify!(&me_pres_rw, vec![acp_allow], &r_set, true);
2457 }
2458
2459 macro_rules! test_acp_create {
2460 (
2461 $ce:expr,
2462 $controls:expr,
2463 $entries:expr,
2464 $expect:expr
2465 ) => {{
2466 let ac = AccessControls::default();
2467 let mut acw = ac.write();
2468 acw.update_create($controls).expect("Failed to update");
2469 let acw = acw;
2470
2471 let res = acw
2472 .create_allow_operation(&mut $ce, $entries)
2473 .expect("op failed");
2474
2475 debug!("result --> {:?}", res);
2476 debug!("expect --> {:?}", $expect);
2477 assert_eq!(res, $expect);
2479 }};
2480 }
2481
2482 #[test]
2483 fn test_access_enforce_create() {
2484 let ev1 = entry_init!(
2485 (Attribute::Class, EntryClass::Account.to_value()),
2486 (Attribute::Name, Value::new_iname("testperson1")),
2487 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2488 );
2489 let r1_set = vec![ev1];
2490
2491 let ev2 = entry_init!(
2492 (Attribute::Class, EntryClass::Account.to_value()),
2493 (Attribute::TestNotAllowed, Value::new_iutf8("notallowed")),
2494 (Attribute::Name, Value::new_iname("testperson1")),
2495 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2496 );
2497
2498 let r2_set = vec![ev2];
2499
2500 let ev3 = entry_init!(
2501 (Attribute::Class, EntryClass::Account.to_value()),
2502 (Attribute::Class, Value::new_iutf8("notallowed")),
2503 (Attribute::Name, Value::new_iname("testperson1")),
2504 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2505 );
2506 let r3_set = vec![ev3];
2507
2508 let ev4 = entry_init!(
2509 (Attribute::Class, EntryClass::Account.to_value()),
2510 (Attribute::Class, EntryClass::Group.to_value()),
2511 (Attribute::Name, Value::new_iname("testperson1")),
2512 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2513 );
2514 let r4_set = vec![ev4];
2515
2516 let ce_admin = CreateEvent::new_impersonate_identity(
2523 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2524 vec![],
2525 );
2526
2527 let acp = AccessControlCreate::from_raw(
2528 "test_create",
2529 Uuid::new_v4(),
2530 UUID_TEST_GROUP_1,
2532 filter_valid!(f_eq(
2535 Attribute::Name,
2536 PartialValue::new_iname("testperson1")
2537 )),
2538 EntryClass::Account.into(),
2540 "class name uuid",
2542 );
2543
2544 let acp2 = AccessControlCreate::from_raw(
2545 "test_create_2",
2546 Uuid::new_v4(),
2547 UUID_TEST_GROUP_1,
2549 filter_valid!(f_eq(
2551 Attribute::Name,
2552 PartialValue::new_iname("testperson1")
2553 )),
2554 EntryClass::Group.into(),
2556 "class name uuid",
2558 );
2559
2560 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
2562 test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
2564 test_acp_create!(&ce_admin, vec![acp.clone()], &r3_set, false);
2566 test_acp_create!(&ce_admin, vec![acp, acp2], &r4_set, false);
2568 }
2569
2570 #[test]
2571 fn test_access_enforce_scope_create() {
2572 let ev1 = entry_init!(
2573 (Attribute::Class, EntryClass::Account.to_value()),
2574 (Attribute::Name, Value::new_iname("testperson1")),
2575 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2576 );
2577 let r1_set = vec![ev1];
2578
2579 let admin = E_TEST_ACCOUNT_1.clone();
2580
2581 let ce_admin_ro = CreateEvent::new_impersonate_identity(
2582 Identity::from_impersonate_entry_readonly(admin.clone()),
2583 vec![],
2584 );
2585
2586 let ce_admin_rw = CreateEvent::new_impersonate_identity(
2587 Identity::from_impersonate_entry_readwrite(admin),
2588 vec![],
2589 );
2590
2591 let acp = AccessControlCreate::from_raw(
2592 "test_create",
2593 Uuid::new_v4(),
2594 UUID_TEST_GROUP_1,
2596 filter_valid!(f_eq(
2599 Attribute::Name,
2600 PartialValue::new_iname("testperson1")
2601 )),
2602 EntryClass::Account.into(),
2604 "class name uuid",
2606 );
2607
2608 test_acp_create!(&ce_admin_ro, vec![acp.clone()], &r1_set, false);
2609
2610 test_acp_create!(&ce_admin_rw, vec![acp], &r1_set, true);
2611 }
2612
2613 macro_rules! test_acp_delete {
2614 (
2615 $de:expr,
2616 $controls:expr,
2617 $entries:expr,
2618 $expect:expr
2619 ) => {{
2620 let ac = AccessControls::default();
2621 let mut acw = ac.write();
2622 acw.update_delete($controls).expect("Failed to update");
2623 let acw = acw;
2624
2625 let res = acw
2626 .delete_allow_operation($de, $entries)
2627 .expect("op failed");
2628
2629 debug!("result --> {:?}", res);
2630 debug!("expect --> {:?}", $expect);
2631 assert_eq!(res, $expect);
2633 }};
2634 }
2635
2636 #[test]
2637 fn test_access_enforce_delete() {
2638 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2639 let r_set = vec![Arc::new(ev1)];
2640
2641 let de_admin = DeleteEvent::new_impersonate_entry(
2642 E_TEST_ACCOUNT_1.clone(),
2643 filter_all!(f_eq(
2644 Attribute::Name,
2645 PartialValue::new_iname("testperson1")
2646 )),
2647 );
2648
2649 let de_anon = DeleteEvent::new_impersonate_entry(
2650 E_TEST_ACCOUNT_2.clone(),
2651 filter_all!(f_eq(
2652 Attribute::Name,
2653 PartialValue::new_iname("testperson1")
2654 )),
2655 );
2656
2657 let acp = AccessControlDelete::from_raw(
2658 "test_delete",
2659 Uuid::new_v4(),
2660 UUID_TEST_GROUP_1,
2662 filter_valid!(f_eq(
2664 Attribute::Name,
2665 PartialValue::new_iname("testperson1")
2666 )),
2667 );
2668
2669 test_acp_delete!(&de_admin, vec![acp.clone()], &r_set, true);
2671 test_acp_delete!(&de_anon, vec![acp], &r_set, false);
2673 }
2674
2675 #[test]
2676 fn test_access_enforce_scope_delete() {
2677 sketching::test_init();
2678 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2679 let r_set = vec![Arc::new(ev1)];
2680
2681 let admin = E_TEST_ACCOUNT_1.clone();
2682
2683 let de_admin_ro = DeleteEvent::new_impersonate_identity(
2684 Identity::from_impersonate_entry_readonly(admin.clone()),
2685 filter_all!(f_eq(
2686 Attribute::Name,
2687 PartialValue::new_iname("testperson1")
2688 )),
2689 );
2690
2691 let de_admin_rw = DeleteEvent::new_impersonate_identity(
2692 Identity::from_impersonate_entry_readwrite(admin),
2693 filter_all!(f_eq(
2694 Attribute::Name,
2695 PartialValue::new_iname("testperson1")
2696 )),
2697 );
2698
2699 let acp = AccessControlDelete::from_raw(
2700 "test_delete",
2701 Uuid::new_v4(),
2702 UUID_TEST_GROUP_1,
2704 filter_valid!(f_eq(
2706 Attribute::Name,
2707 PartialValue::new_iname("testperson1")
2708 )),
2709 );
2710
2711 test_acp_delete!(&de_admin_ro, vec![acp.clone()], &r_set, false);
2712
2713 test_acp_delete!(&de_admin_rw, vec![acp], &r_set, true);
2714 }
2715
2716 macro_rules! test_acp_effective_permissions {
2717 (
2718 $ident:expr,
2719 $attrs:expr,
2720 $search_controls:expr,
2721 $modify_controls:expr,
2722 $entries:expr,
2723 $expect:expr
2724 ) => {{
2725 let ac = AccessControls::default();
2726 let mut acw = ac.write();
2727 acw.update_search($search_controls)
2728 .expect("Failed to update");
2729 acw.update_modify($modify_controls)
2730 .expect("Failed to update");
2731 let acw = acw;
2732
2733 let res = acw
2734 .effective_permission_check($ident, $attrs, $entries)
2735 .expect("Failed to apply effective_permission_check");
2736
2737 debug!("result --> {:?}", res);
2738 debug!("expect --> {:?}", $expect);
2739 assert_eq!(res, $expect);
2741 }};
2742 }
2743
2744 #[test]
2745 fn test_access_effective_permission_check_1() {
2746 sketching::test_init();
2747
2748 let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
2749
2750 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2751
2752 let r_set = vec![Arc::new(ev1)];
2753
2754 test_acp_effective_permissions!(
2755 &admin,
2756 None,
2757 vec![AccessControlSearch::from_raw(
2758 "test_acp",
2759 Uuid::new_v4(),
2760 UUID_TEST_GROUP_1,
2762 filter_valid!(f_eq(
2764 Attribute::Name,
2765 PartialValue::new_iname("testperson1")
2766 )),
2767 Attribute::Name.as_ref(),
2769 )],
2770 vec![],
2771 &r_set,
2772 vec![AccessEffectivePermission {
2773 ident: UUID_TEST_ACCOUNT_1,
2774 delete: false,
2775 target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2776 search: Access::Allow(btreeset![Attribute::Name]),
2777 modify_pres: Access::Allow(BTreeSet::new()),
2778 modify_rem: Access::Allow(BTreeSet::new()),
2779 modify_pres_class: AccessClass::Allow(BTreeSet::new()),
2780 modify_rem_class: AccessClass::Allow(BTreeSet::new()),
2781 }]
2782 )
2783 }
2784
2785 #[test]
2786 fn test_access_effective_permission_check_2() {
2787 sketching::test_init();
2788
2789 let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
2790
2791 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2792
2793 let r_set = vec![Arc::new(ev1)];
2794
2795 test_acp_effective_permissions!(
2796 &admin,
2797 None,
2798 vec![],
2799 vec![AccessControlModify::from_raw(
2800 "test_acp",
2801 Uuid::new_v4(),
2802 UUID_TEST_GROUP_1,
2804 filter_valid!(f_eq(
2806 Attribute::Name,
2807 PartialValue::new_iname("testperson1")
2808 )),
2809 Attribute::Name.as_ref(),
2811 Attribute::Name.as_ref(),
2812 EntryClass::Object.into(),
2813 EntryClass::Object.into(),
2814 )],
2815 &r_set,
2816 vec![AccessEffectivePermission {
2817 ident: UUID_TEST_ACCOUNT_1,
2818 delete: false,
2819 target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2820 search: Access::Allow(BTreeSet::new()),
2821 modify_pres: Access::Allow(btreeset![Attribute::Name]),
2822 modify_rem: Access::Allow(btreeset![Attribute::Name]),
2823 modify_pres_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
2824 modify_rem_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
2825 }]
2826 )
2827 }
2828
2829 #[test]
2830 fn test_access_sync_authority_create() {
2831 sketching::test_init();
2832
2833 let ce_admin = CreateEvent::new_impersonate_identity(
2834 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2835 vec![],
2836 );
2837
2838 let ev1 = entry_init!(
2840 (Attribute::Class, EntryClass::Account.to_value()),
2841 (Attribute::Name, Value::new_iname("testperson1")),
2842 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2843 );
2844 let r1_set = vec![ev1];
2845
2846 let ev2 = entry_init!(
2847 (Attribute::Class, EntryClass::Account.to_value()),
2848 (Attribute::Class, EntryClass::SyncObject.to_value()),
2849 (Attribute::Name, Value::new_iname("testperson1")),
2850 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2851 );
2852 let r2_set = vec![ev2];
2853
2854 let acp = AccessControlCreate::from_raw(
2855 "test_create",
2856 Uuid::new_v4(),
2857 UUID_TEST_GROUP_1,
2859 filter_valid!(f_eq(
2862 Attribute::Name,
2863 PartialValue::new_iname("testperson1")
2864 )),
2865 "account sync_object",
2867 "class name uuid",
2869 );
2870
2871 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
2873 test_acp_create!(&ce_admin, vec![acp], &r2_set, false);
2875 }
2876
2877 #[test]
2878 fn test_access_sync_authority_delete() {
2879 sketching::test_init();
2880
2881 let ev1 = entry_init!(
2882 (Attribute::Class, EntryClass::Account.to_value()),
2883 (Attribute::Name, Value::new_iname("testperson1")),
2884 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2885 )
2886 .into_sealed_committed();
2887 let r1_set = vec![Arc::new(ev1)];
2888
2889 let ev2 = entry_init!(
2890 (Attribute::Class, EntryClass::Account.to_value()),
2891 (Attribute::Class, EntryClass::SyncObject.to_value()),
2892 (Attribute::Name, Value::new_iname("testperson1")),
2893 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2894 )
2895 .into_sealed_committed();
2896 let r2_set = vec![Arc::new(ev2)];
2897
2898 let de_admin = DeleteEvent::new_impersonate_entry(
2899 E_TEST_ACCOUNT_1.clone(),
2900 filter_all!(f_eq(
2901 Attribute::Name,
2902 PartialValue::new_iname("testperson1")
2903 )),
2904 );
2905
2906 let acp = AccessControlDelete::from_raw(
2907 "test_delete",
2908 Uuid::new_v4(),
2909 UUID_TEST_GROUP_1,
2911 filter_valid!(f_eq(
2913 Attribute::Name,
2914 PartialValue::new_iname("testperson1")
2915 )),
2916 );
2917
2918 test_acp_delete!(&de_admin, vec![acp.clone()], &r1_set, true);
2920 test_acp_delete!(&de_admin, vec![acp], &r2_set, false);
2922 }
2923
2924 #[test]
2925 fn test_access_sync_authority_modify() {
2926 sketching::test_init();
2927
2928 let ev1 = entry_init!(
2929 (Attribute::Class, EntryClass::Account.to_value()),
2930 (Attribute::Name, Value::new_iname("testperson1")),
2931 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2932 )
2933 .into_sealed_committed();
2934 let r1_set = vec![Arc::new(ev1)];
2935
2936 let sync_uuid = Uuid::new_v4();
2937 let ev2 = entry_init!(
2938 (Attribute::Class, EntryClass::Account.to_value()),
2939 (Attribute::Class, EntryClass::SyncObject.to_value()),
2940 (Attribute::SyncParentUuid, Value::Refer(sync_uuid)),
2941 (Attribute::Name, Value::new_iname("testperson1")),
2942 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2943 )
2944 .into_sealed_committed();
2945 let r2_set = vec![Arc::new(ev2)];
2946
2947 let acp_allow = AccessControlModify::from_raw(
2949 "test_modify_allow",
2950 Uuid::new_v4(),
2951 UUID_TEST_GROUP_1,
2953 filter_valid!(f_eq(
2955 Attribute::Name,
2956 PartialValue::new_iname("testperson1")
2957 )),
2958 &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
2960 &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
2962 EntryClass::Account.into(),
2964 EntryClass::Account.into(),
2965 );
2966
2967 let me_pres = ModifyEvent::new_impersonate_entry(
2971 E_TEST_ACCOUNT_1.clone(),
2972 filter_all!(f_eq(
2973 Attribute::Name,
2974 PartialValue::new_iname("testperson1")
2975 )),
2976 modlist!([m_pres(
2977 Attribute::UserAuthTokenSession,
2978 &Value::new_iname("value")
2979 )]),
2980 );
2981 let me_rem = ModifyEvent::new_impersonate_entry(
2983 E_TEST_ACCOUNT_1.clone(),
2984 filter_all!(f_eq(
2985 Attribute::Name,
2986 PartialValue::new_iname("testperson1")
2987 )),
2988 modlist!([m_remove(
2989 Attribute::UserAuthTokenSession,
2990 &PartialValue::new_iname("value")
2991 )]),
2992 );
2993 let me_purge = ModifyEvent::new_impersonate_entry(
2995 E_TEST_ACCOUNT_1.clone(),
2996 filter_all!(f_eq(
2997 Attribute::Name,
2998 PartialValue::new_iname("testperson1")
2999 )),
3000 modlist!([m_purge(Attribute::UserAuthTokenSession)]),
3001 );
3002
3003 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
3005 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r1_set, true);
3007 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r1_set, true);
3009
3010 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, true);
3012 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, true);
3014 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, true);
3016
3017 let me_pres = ModifyEvent::new_impersonate_entry(
3019 E_TEST_ACCOUNT_1.clone(),
3020 filter_all!(f_eq(
3021 Attribute::Name,
3022 PartialValue::new_iname("testperson1")
3023 )),
3024 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
3025 );
3026 let me_rem = ModifyEvent::new_impersonate_entry(
3028 E_TEST_ACCOUNT_1.clone(),
3029 filter_all!(f_eq(
3030 Attribute::Name,
3031 PartialValue::new_iname("testperson1")
3032 )),
3033 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
3034 );
3035 let me_purge = ModifyEvent::new_impersonate_entry(
3037 E_TEST_ACCOUNT_1.clone(),
3038 filter_all!(f_eq(
3039 Attribute::Name,
3040 PartialValue::new_iname("testperson1")
3041 )),
3042 modlist!([m_purge(Attribute::Name)]),
3043 );
3044
3045 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
3047 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, false);
3049 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, false);
3051
3052 test_acp_modify!(
3057 &me_pres,
3058 vec![acp_allow.clone()],
3059 sync_uuid,
3060 Attribute::Name,
3061 &r2_set,
3062 true
3063 );
3064 test_acp_modify!(
3066 &me_rem,
3067 vec![acp_allow.clone()],
3068 sync_uuid,
3069 Attribute::Name,
3070 &r2_set,
3071 true
3072 );
3073 test_acp_modify!(
3075 &me_purge,
3076 vec![acp_allow],
3077 sync_uuid,
3078 Attribute::Name,
3079 &r2_set,
3080 true
3081 );
3082 }
3083
3084 #[test]
3085 fn test_access_oauth2_dyn_search() {
3086 sketching::test_init();
3087 let rs_uuid = Uuid::new_v4();
3090 let ev1 = entry_init!(
3091 (Attribute::Class, EntryClass::Object.to_value()),
3092 (
3093 Attribute::Class,
3094 EntryClass::OAuth2ResourceServer.to_value()
3095 ),
3096 (
3097 Attribute::Class,
3098 EntryClass::OAuth2ResourceServerBasic.to_value()
3099 ),
3100 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3101 (Attribute::Name, Value::new_iname("test_resource_server")),
3102 (
3103 Attribute::DisplayName,
3104 Value::new_utf8s("test_resource_server")
3105 ),
3106 (
3107 Attribute::OAuth2RsOriginLanding,
3108 Value::new_url_s("https://demo.example.com").unwrap()
3109 ),
3110 (
3111 Attribute::OAuth2RsOrigin,
3112 Value::new_url_s("app://hidden").unwrap()
3113 ),
3114 (
3115 Attribute::OAuth2RsScopeMap,
3116 Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["groups".to_string()])
3117 .expect("invalid oauthscope")
3118 ),
3119 (
3120 Attribute::OAuth2RsSupScopeMap,
3121 Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["supplement".to_string()])
3122 .expect("invalid oauthscope")
3123 ),
3124 (
3125 Attribute::OAuth2AllowInsecureClientDisablePkce,
3126 Value::new_bool(true)
3127 ),
3128 (
3129 Attribute::OAuth2JwtLegacyCryptoEnable,
3130 Value::new_bool(false)
3131 ),
3132 (Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
3133 )
3134 .into_sealed_committed();
3135
3136 let ev1_reduced = entry_init!(
3137 (Attribute::Class, EntryClass::Object.to_value()),
3138 (
3139 Attribute::Class,
3140 EntryClass::OAuth2ResourceServer.to_value()
3141 ),
3142 (
3143 Attribute::Class,
3144 EntryClass::OAuth2ResourceServerBasic.to_value()
3145 ),
3146 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3147 (Attribute::Name, Value::new_iname("test_resource_server")),
3148 (
3149 Attribute::DisplayName,
3150 Value::new_utf8s("test_resource_server")
3151 ),
3152 (
3153 Attribute::OAuth2RsOriginLanding,
3154 Value::new_url_s("https://demo.example.com").unwrap()
3155 )
3156 )
3157 .into_sealed_committed();
3158
3159 let ev2 = entry_init!(
3160 (Attribute::Class, EntryClass::Object.to_value()),
3161 (
3162 Attribute::Class,
3163 EntryClass::OAuth2ResourceServer.to_value()
3164 ),
3165 (
3166 Attribute::Class,
3167 EntryClass::OAuth2ResourceServerBasic.to_value()
3168 ),
3169 (Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
3170 (Attribute::Name, Value::new_iname("second_resource_server")),
3171 (
3172 Attribute::DisplayName,
3173 Value::new_utf8s("second_resource_server")
3174 ),
3175 (
3176 Attribute::OAuth2RsOriginLanding,
3177 Value::new_url_s("https://noaccess.example.com").unwrap()
3178 ),
3179 (
3180 Attribute::OAuth2RsOrigin,
3181 Value::new_url_s("app://hidden").unwrap()
3182 ),
3183 (
3184 Attribute::OAuth2RsScopeMap,
3185 Value::new_oauthscopemap(UUID_SYSTEM_ADMINS, btreeset!["groups".to_string()])
3186 .expect("invalid oauthscope")
3187 ),
3188 (
3189 Attribute::OAuth2RsSupScopeMap,
3190 Value::new_oauthscopemap(
3191 UUID_TEST_GROUP_1,
3193 btreeset!["supplement".to_string()]
3194 )
3195 .expect("invalid oauthscope")
3196 ),
3197 (
3198 Attribute::OAuth2AllowInsecureClientDisablePkce,
3199 Value::new_bool(true)
3200 ),
3201 (
3202 Attribute::OAuth2JwtLegacyCryptoEnable,
3203 Value::new_bool(false)
3204 ),
3205 (Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
3206 )
3207 .into_sealed_committed();
3208
3209 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
3210
3211 let se_a = SearchEvent::new_impersonate_entry(
3213 E_TEST_ACCOUNT_1.clone(),
3214 filter_all!(f_pres(Attribute::Name)),
3215 );
3216 let ex_a = vec![Arc::new(ev1)];
3217 let ex_a_reduced = vec![ev1_reduced];
3218
3219 test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
3220 test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
3221
3222 let anon: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
3224 let mut anon = anon.into_invalid_new();
3225 anon.set_ava_set(&Attribute::MemberOf, ValueSetRefer::new(UUID_TEST_GROUP_1));
3226
3227 let anon = Arc::new(anon.into_sealed_committed());
3228
3229 let se_anon =
3230 SearchEvent::new_impersonate_entry(anon, filter_all!(f_pres(Attribute::Name)));
3231 let ex_anon = vec![];
3232 test_acp_search!(&se_anon, vec![], r_set.clone(), ex_anon);
3233
3234 let se_b = SearchEvent::new_impersonate_entry(
3236 E_TEST_ACCOUNT_2.clone(),
3237 filter_all!(f_pres(Attribute::Name)),
3238 );
3239 let ex_b = vec![];
3240
3241 test_acp_search!(&se_b, vec![], r_set, ex_b);
3242 }
3243
3244 #[test]
3245 fn test_access_sync_account_dyn_search() {
3246 sketching::test_init();
3247 let sync_uuid = Uuid::new_v4();
3252 let portal_url = Url::parse("https://localhost/portal").unwrap();
3253
3254 let ev1 = entry_init!(
3255 (Attribute::Class, EntryClass::Object.to_value()),
3256 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3257 (Attribute::Uuid, Value::Uuid(sync_uuid)),
3258 (Attribute::Name, Value::new_iname("test_sync_account")),
3259 (
3260 Attribute::SyncCredentialPortal,
3261 Value::Url(portal_url.clone())
3262 )
3263 )
3264 .into_sealed_committed();
3265
3266 let ev1_reduced = entry_init!(
3267 (Attribute::Class, EntryClass::Object.to_value()),
3268 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3269 (Attribute::Uuid, Value::Uuid(sync_uuid)),
3270 (
3271 Attribute::SyncCredentialPortal,
3272 Value::Url(portal_url.clone())
3273 )
3274 )
3275 .into_sealed_committed();
3276
3277 let ev2 = entry_init!(
3278 (Attribute::Class, EntryClass::Object.to_value()),
3279 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3280 (Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
3281 (Attribute::Name, Value::new_iname("test_sync_account")),
3282 (
3283 Attribute::SyncCredentialPortal,
3284 Value::Url(portal_url.clone())
3285 )
3286 )
3287 .into_sealed_committed();
3288
3289 let sync_test_account: Arc<EntrySealedCommitted> = Arc::new(
3290 entry_init!(
3291 (Attribute::Class, EntryClass::Object.to_value()),
3292 (Attribute::Class, EntryClass::Account.to_value()),
3293 (Attribute::Class, EntryClass::SyncObject.to_value()),
3294 (Attribute::Name, Value::new_iname("test_account_1")),
3295 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3296 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3297 (Attribute::SyncParentUuid, Value::Refer(sync_uuid))
3298 )
3299 .into_sealed_committed(),
3300 );
3301
3302 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
3304
3305 let se_a = SearchEvent::new_impersonate_entry(
3306 sync_test_account,
3307 filter_all!(f_pres(Attribute::SyncCredentialPortal)),
3308 );
3309 let ex_a = vec![Arc::new(ev1)];
3310 let ex_a_reduced = vec![ev1_reduced];
3311
3312 test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
3313 test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
3314
3315 let se_b = SearchEvent::new_impersonate_entry(
3317 E_TEST_ACCOUNT_2.clone(),
3318 filter_all!(f_pres(Attribute::SyncCredentialPortal)),
3319 );
3320 let ex_b = vec![];
3321
3322 test_acp_search!(&se_b, vec![], r_set, ex_b);
3323 }
3324
3325 #[test]
3326 fn test_access_entry_managed_by_search() {
3327 sketching::test_init();
3328
3329 let test_entry = Arc::new(
3330 entry_init!(
3331 (Attribute::Class, EntryClass::Object.to_value()),
3332 (Attribute::Name, Value::new_iname("testperson1")),
3333 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3334 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3335 )
3336 .into_sealed_committed(),
3337 );
3338
3339 let data_set = vec![test_entry.clone()];
3340
3341 let se_a = SearchEvent::new_impersonate_entry(
3342 E_TEST_ACCOUNT_1.clone(),
3343 filter_all!(f_pres(Attribute::Name)),
3344 );
3345 let expect_a = vec![test_entry];
3346
3347 let se_b = SearchEvent::new_impersonate_entry(
3348 E_TEST_ACCOUNT_2.clone(),
3349 filter_all!(f_pres(Attribute::Name)),
3350 );
3351 let expect_b = vec![];
3352
3353 let acp = AccessControlSearch::from_managed_by(
3354 "test_acp",
3355 Uuid::new_v4(),
3356 AccessControlTarget::Scope(filter_valid!(f_eq(
3358 Attribute::Name,
3359 PartialValue::new_iname("testperson1")
3360 ))),
3361 Attribute::Name.as_ref(),
3364 );
3365
3366 test_acp_search!(&se_a, vec![acp.clone()], data_set.clone(), expect_a);
3368
3369 test_acp_search!(&se_b, vec![acp], data_set, expect_b);
3371 }
3372
3373 #[test]
3374 fn test_access_entry_managed_by_create() {
3375 sketching::test_init();
3376
3377 let test_entry = entry_init!(
3378 (Attribute::Class, EntryClass::Object.to_value()),
3379 (Attribute::Name, Value::new_iname("testperson1")),
3380 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3381 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3382 );
3383
3384 let data_set = vec![test_entry];
3385
3386 let ce = CreateEvent::new_impersonate_identity(
3387 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
3388 vec![],
3389 );
3390
3391 let acp = AccessControlCreate::from_managed_by(
3392 "test_create",
3393 Uuid::new_v4(),
3394 AccessControlTarget::Scope(filter_valid!(f_eq(
3395 Attribute::Name,
3396 PartialValue::new_iname("testperson1")
3397 ))),
3398 EntryClass::Account.into(),
3400 "class name uuid",
3402 );
3403
3404 test_acp_create!(&ce, vec![acp.clone()], &data_set, false);
3407 }
3408
3409 #[test]
3410 fn test_access_entry_managed_by_modify() {
3411 let test_entry = Arc::new(
3412 entry_init!(
3413 (Attribute::Class, EntryClass::Object.to_value()),
3414 (Attribute::Name, Value::new_iname("testperson1")),
3415 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3416 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3417 )
3418 .into_sealed_committed(),
3419 );
3420
3421 let data_set = vec![test_entry];
3422
3423 let me_pres = ModifyEvent::new_impersonate_entry(
3425 E_TEST_ACCOUNT_1.clone(),
3426 filter_all!(f_eq(
3427 Attribute::Name,
3428 PartialValue::new_iname("testperson1")
3429 )),
3430 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
3431 );
3432 let me_rem = ModifyEvent::new_impersonate_entry(
3434 E_TEST_ACCOUNT_1.clone(),
3435 filter_all!(f_eq(
3436 Attribute::Name,
3437 PartialValue::new_iname("testperson1")
3438 )),
3439 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
3440 );
3441 let me_purge = ModifyEvent::new_impersonate_entry(
3443 E_TEST_ACCOUNT_1.clone(),
3444 filter_all!(f_eq(
3445 Attribute::Name,
3446 PartialValue::new_iname("testperson1")
3447 )),
3448 modlist!([m_purge(Attribute::Name)]),
3449 );
3450
3451 let acp_allow = AccessControlModify::from_managed_by(
3452 "test_modify_allow",
3453 Uuid::new_v4(),
3454 AccessControlTarget::Scope(filter_valid!(f_eq(
3456 Attribute::Name,
3457 PartialValue::new_iname("testperson1")
3458 ))),
3459 "name class",
3461 "name class",
3463 EntryClass::Account.into(),
3465 EntryClass::Account.into(),
3466 );
3467
3468 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &data_set, true);
3470 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &data_set, true);
3472 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &data_set, true);
3474 }
3475
3476 #[test]
3477 fn test_access_entry_managed_by_delete() {
3478 let test_entry = Arc::new(
3479 entry_init!(
3480 (Attribute::Class, EntryClass::Object.to_value()),
3481 (Attribute::Name, Value::new_iname("testperson1")),
3482 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3483 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3484 )
3485 .into_sealed_committed(),
3486 );
3487
3488 let data_set = vec![test_entry];
3489
3490 let de_a = DeleteEvent::new_impersonate_entry(
3491 E_TEST_ACCOUNT_1.clone(),
3492 filter_all!(f_eq(
3493 Attribute::Name,
3494 PartialValue::new_iname("testperson1")
3495 )),
3496 );
3497
3498 let de_b = DeleteEvent::new_impersonate_entry(
3499 E_TEST_ACCOUNT_2.clone(),
3500 filter_all!(f_eq(
3501 Attribute::Name,
3502 PartialValue::new_iname("testperson1")
3503 )),
3504 );
3505
3506 let acp = AccessControlDelete::from_managed_by(
3507 "test_delete",
3508 Uuid::new_v4(),
3509 AccessControlTarget::Scope(filter_valid!(f_eq(
3511 Attribute::Name,
3512 PartialValue::new_iname("testperson1")
3513 ))),
3514 );
3515
3516 test_acp_delete!(&de_a, vec![acp.clone()], &data_set, true);
3518 test_acp_delete!(&de_b, vec![acp], &data_set, false);
3520 }
3521
3522 #[test]
3523 fn test_access_delete_protect_system_ranges() {
3524 let ev1: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
3525 let ev1 = ev1.into_sealed_committed();
3526 let r_set = vec![Arc::new(ev1)];
3527
3528 let de_account = DeleteEvent::new_impersonate_entry(
3529 E_TEST_ACCOUNT_1.clone(),
3530 filter_all!(f_eq(
3531 Attribute::Name,
3532 PartialValue::new_iname("testperson1")
3533 )),
3534 );
3535
3536 let acp = AccessControlDelete::from_raw(
3537 "test_delete",
3538 Uuid::new_v4(),
3539 UUID_TEST_GROUP_1,
3540 filter_valid!(f_eq(Attribute::Name, PartialValue::new_iname("anonymous"))),
3542 );
3543
3544 test_acp_delete!(&de_account, vec![acp], &r_set, false);
3546 }
3547
3548 #[test]
3549 fn test_access_sync_memberof_implies_directmemberof() {
3550 sketching::test_init();
3551
3552 let ev1 = entry_init!(
3553 (Attribute::Class, EntryClass::Object.to_value()),
3554 (Attribute::Name, Value::new_iname("test_account_1")),
3555 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3556 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3557 (Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
3558 )
3559 .into_sealed_committed();
3560 let r_set = vec![Arc::new(ev1)];
3561
3562 let exv1 = entry_init!(
3563 (Attribute::Name, Value::new_iname("test_account_1")),
3564 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3565 (Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
3566 )
3567 .into_sealed_committed();
3568
3569 let ex_anon_some = vec![exv1];
3570
3571 let se_anon_ro = SearchEvent::new_impersonate_identity(
3572 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
3573 filter_all!(f_pres(Attribute::Name)),
3574 );
3575
3576 let acp = AccessControlSearch::from_raw(
3577 "test_acp",
3578 Uuid::new_v4(),
3579 UUID_TEST_GROUP_1,
3581 filter_valid!(f_eq(
3583 Attribute::Uuid,
3584 PartialValue::Uuid(UUID_TEST_ACCOUNT_1)
3585 )),
3586 format!("{} {}", Attribute::Name, Attribute::MemberOf).as_str(),
3589 );
3590
3591 test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
3593 }
3594
3595 #[test]
3596 fn test_access_protected_deny_create() {
3597 sketching::test_init();
3598
3599 let ev1 = entry_init!(
3600 (Attribute::Class, EntryClass::Account.to_value()),
3601 (Attribute::Name, Value::new_iname("testperson1")),
3602 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3603 );
3604 let r1_set = vec![ev1];
3605
3606 let ev2 = entry_init!(
3607 (Attribute::Class, EntryClass::Account.to_value()),
3608 (Attribute::Class, EntryClass::System.to_value()),
3609 (Attribute::Name, Value::new_iname("testperson1")),
3610 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3611 );
3612
3613 let r2_set = vec![ev2];
3614
3615 let ce_admin = CreateEvent::new_impersonate_identity(
3616 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
3617 vec![],
3618 );
3619
3620 let acp = AccessControlCreate::from_raw(
3621 "test_create",
3622 Uuid::new_v4(),
3623 UUID_TEST_GROUP_1,
3625 filter_valid!(f_eq(
3628 Attribute::Name,
3629 PartialValue::new_iname("testperson1")
3630 )),
3631 EntryClass::Account.into(),
3633 "class name uuid",
3635 );
3636
3637 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
3639 test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
3641 }
3642
3643 #[test]
3644 fn test_access_protected_deny_delete() {
3645 sketching::test_init();
3646
3647 let ev1 = entry_init!(
3648 (Attribute::Class, EntryClass::Account.to_value()),
3649 (Attribute::Name, Value::new_iname("testperson1")),
3650 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3651 )
3652 .into_sealed_committed();
3653 let r1_set = vec![Arc::new(ev1)];
3654
3655 let ev2 = entry_init!(
3656 (Attribute::Class, EntryClass::Account.to_value()),
3657 (Attribute::Class, EntryClass::System.to_value()),
3658 (Attribute::Name, Value::new_iname("testperson1")),
3659 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3660 )
3661 .into_sealed_committed();
3662
3663 let r2_set = vec![Arc::new(ev2)];
3664
3665 let de = DeleteEvent::new_impersonate_entry(
3666 E_TEST_ACCOUNT_1.clone(),
3667 filter_all!(f_eq(
3668 Attribute::Name,
3669 PartialValue::new_iname("testperson1")
3670 )),
3671 );
3672
3673 let acp = AccessControlDelete::from_raw(
3674 "test_delete",
3675 Uuid::new_v4(),
3676 UUID_TEST_GROUP_1,
3678 filter_valid!(f_eq(
3680 Attribute::Name,
3681 PartialValue::new_iname("testperson1")
3682 )),
3683 );
3684
3685 test_acp_delete!(&de, vec![acp.clone()], &r1_set, true);
3687 test_acp_delete!(&de, vec![acp.clone()], &r2_set, false);
3689 }
3690
3691 #[test]
3692 fn test_access_protected_deny_modify() {
3693 sketching::test_init();
3694
3695 let ev1 = entry_init!(
3696 (Attribute::Class, EntryClass::Account.to_value()),
3697 (Attribute::Name, Value::new_iname("testperson1")),
3698 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3699 )
3700 .into_sealed_committed();
3701 let r1_set = vec![Arc::new(ev1)];
3702
3703 let ev2 = entry_init!(
3704 (Attribute::Class, EntryClass::Account.to_value()),
3705 (Attribute::Class, EntryClass::System.to_value()),
3706 (Attribute::Name, Value::new_iname("testperson1")),
3707 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3708 )
3709 .into_sealed_committed();
3710
3711 let r2_set = vec![Arc::new(ev2)];
3712
3713 let acp_allow = AccessControlModify::from_raw(
3715 "test_modify_allow",
3716 Uuid::new_v4(),
3717 UUID_TEST_GROUP_1,
3719 filter_valid!(f_eq(
3721 Attribute::Name,
3722 PartialValue::new_iname("testperson1")
3723 )),
3724 "displayname class",
3726 "displayname class",
3728 "system recycled",
3730 "system recycled",
3731 );
3732
3733 let me_pres = ModifyEvent::new_impersonate_entry(
3734 E_TEST_ACCOUNT_1.clone(),
3735 filter_all!(f_eq(
3736 Attribute::Name,
3737 PartialValue::new_iname("testperson1")
3738 )),
3739 modlist!([m_pres(Attribute::DisplayName, &Value::new_utf8s("value"))]),
3740 );
3741
3742 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
3744
3745 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
3747
3748 let me_rem_sys = ModifyEvent::new_impersonate_entry(
3750 E_TEST_ACCOUNT_1.clone(),
3751 filter_all!(f_eq(
3752 Attribute::Class,
3753 PartialValue::new_iname("testperson1")
3754 )),
3755 modlist!([m_remove(
3756 Attribute::Class,
3757 &EntryClass::System.to_partialvalue()
3758 )]),
3759 );
3760
3761 test_acp_modify!(&me_rem_sys, vec![acp_allow.clone()], &r2_set, false);
3762
3763 let me_pres = ModifyEvent::new_impersonate_entry(
3765 E_TEST_ACCOUNT_1.clone(),
3766 filter_all!(f_eq(
3767 Attribute::Name,
3768 PartialValue::new_iname("testperson1")
3769 )),
3770 modlist!([m_pres(Attribute::Class, &EntryClass::Recycled.to_value())]),
3771 );
3772
3773 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, false);
3774 }
3775}