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 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 &entry,
457 &search_related_acp,
458 &do_check.modify_related_acp,
459 &do_check.delete_related_acp,
460 do_check.sync_agmts,
461 )
462 })
463 .map(Box::new);
464
465 Some(entry.reduce_attributes(&reduced_attrs, effective_permissions))
466 }
467 }
468
469 })
471 .collect();
472
473 if allowed_entries.is_empty() {
474 if !entries_is_empty {
475 security_access!("reduced to empty set on all entries ❌");
476 }
477 } else {
478 debug!(
479 "attribute set reduced on {} entries ✅",
480 allowed_entries.len()
481 );
482 }
483
484 Ok(allowed_entries)
485 }
486
487 #[instrument(level = "trace", name = "access::modify_related_acp", skip_all)]
488 fn modify_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlModifyResolved<'b>> {
489 let modify_state = self.get_modify();
491 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
492
493 let ident_memberof = ident.get_memberof();
494
495 let related_acp: Vec<_> = modify_state
498 .iter()
499 .filter_map(|acs| {
500 trace!(acs_name = ?acs.acp.name);
501 let (receiver_condition, target_condition) = resolve_access_conditions(
502 ident,
503 ident_memberof,
504 &acs.acp.receiver,
505 &acs.acp.target,
506 acp_resolve_filter_cache,
507 )?;
508
509 Some(AccessControlModifyResolved {
510 acp: acs,
511 receiver_condition,
512 target_condition,
513 })
514 })
515 .collect();
516
517 related_acp
518 }
519
520 #[instrument(level = "debug", name = "access::modify_allow_operation", skip_all)]
521 fn modify_allow_operation(
522 &self,
523 me: &ModifyEvent,
524 entries: &[Arc<EntrySealedCommitted>],
525 ) -> Result<bool, OperationError> {
526 let disallow = me
528 .modlist
529 .iter()
530 .any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref()));
531
532 if disallow {
533 security_access!("Disallowing purge {} in modification", Attribute::Class);
534 return Ok(false);
535 }
536
537 let related_acp: Vec<_> = self.modify_related_acp(&me.ident);
540
541 let requested_pres: BTreeSet<Attribute> = me
543 .modlist
544 .iter()
545 .filter_map(|m| match m {
546 Modify::Present(a, _) | Modify::Set(a, _) => Some(a.clone()),
547 Modify::Removed(..) | Modify::Assert(..) | Modify::Purged(_) => None,
548 })
549 .collect();
550
551 let requested_rem: BTreeSet<Attribute> = me
552 .modlist
553 .iter()
554 .filter_map(|m| match m {
555 Modify::Set(a, _) | Modify::Removed(a, _) | Modify::Purged(a) => Some(a.clone()),
556 Modify::Present(..) | Modify::Assert(..) => None,
557 })
558 .collect();
559
560 let mut requested_pres_classes: BTreeSet<&str> = Default::default();
564 let mut requested_rem_classes: BTreeSet<&str> = Default::default();
565
566 for modify in me.modlist.iter() {
567 match modify {
568 Modify::Present(a, v) if a == Attribute::Class.as_ref() => {
569 requested_pres_classes.extend(v.to_str())
576 }
577 Modify::Removed(a, v) if a == Attribute::Class.as_ref() => {
578 requested_rem_classes.extend(v.to_str())
579 }
580 Modify::Set(a, v) if a == Attribute::Class.as_ref() => {
581 requested_pres_classes.extend(v.as_iutf8_iter().into_iter().flatten());
587 requested_rem_classes.extend(v.as_iutf8_iter().into_iter().flatten());
588 }
589 _ => {}
590 }
591 }
592
593 debug!(?requested_pres, "Requested present attribute set");
594 debug!(?requested_rem, "Requested remove attribute set");
595 debug!(?requested_pres_classes, "Requested present class set");
596 debug!(?requested_rem_classes, "Requested remove class set");
597
598 let sync_agmts = self.get_sync_agreements();
599
600 let r = entries.iter().all(|e| {
601 debug!(entry_id = %e.get_display_id());
602
603 match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
604 ModifyResult::Deny => false,
605 ModifyResult::Grant => true,
606 ModifyResult::Allow {
607 pres,
608 rem,
609 pres_cls,
610 rem_cls,
611 } => {
612 let mut decision = true;
613
614 if !requested_pres.is_subset(&pres) {
615 security_error!("requested_pres is not a subset of allowed");
616 security_error!(
617 "requested_pres: {:?} !⊆ allowed: {:?}",
618 requested_pres,
619 pres
620 );
621 decision = false
622 };
623
624 if !requested_rem.is_subset(&rem) {
625 security_error!("requested_rem is not a subset of allowed");
626 security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
627 decision = false;
628 };
629
630 if !requested_pres_classes.is_subset(&pres_cls) {
631 security_error!("requested_pres_classes is not a subset of allowed");
632 security_error!(
633 "requested_pres_classes: {:?} !⊆ allowed: {:?}",
634 requested_pres_classes,
635 pres_cls
636 );
637 decision = false;
638 };
639
640 if !requested_rem_classes.is_subset(&rem_cls) {
641 security_error!("requested_rem_classes is not a subset of allowed");
642 security_error!(
643 "requested_rem_classes: {:?} !⊆ allowed: {:?}",
644 requested_rem_classes,
645 rem_cls
646 );
647 decision = false;
648 }
649
650 if decision {
651 debug!("passed pres, rem, classes check.");
652 }
653
654 decision
656 }
657 }
658 });
659
660 if r {
661 debug!("allowed modify of {} entries ✅", entries.len());
662 } else {
663 security_access!("denied ❌ - modify may not proceed");
664 }
665 Ok(r)
666 }
667
668 #[instrument(
669 level = "debug",
670 name = "access::batch_modify_allow_operation",
671 skip_all
672 )]
673 fn batch_modify_allow_operation(
674 &self,
675 me: &BatchModifyEvent,
676 entries: &[Arc<EntrySealedCommitted>],
677 ) -> Result<bool, OperationError> {
678 let related_acp = self.modify_related_acp(&me.ident);
681
682 let r = entries.iter().all(|e| {
683 let Some(modlist) = me.modset.get(&e.get_uuid()) else {
687 security_access!(
688 "modlist not present for {}, failing operation.",
689 e.get_uuid()
690 );
691 return false;
692 };
693
694 let disallow = modlist
695 .iter()
696 .any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref()));
697
698 if disallow {
699 security_access!("Disallowing purge in modification");
700 return false;
701 }
702
703 let requested_pres: BTreeSet<Attribute> = modlist
705 .iter()
706 .filter_map(|m| match m {
707 Modify::Present(a, _) => Some(a.clone()),
708 _ => None,
709 })
710 .collect();
711
712 let requested_rem: BTreeSet<Attribute> = modlist
713 .iter()
714 .filter_map(|m| match m {
715 Modify::Removed(a, _) => Some(a.clone()),
716 Modify::Purged(a) => Some(a.clone()),
717 _ => None,
718 })
719 .collect();
720
721 let mut requested_pres_classes: BTreeSet<&str> = Default::default();
722 let mut requested_rem_classes: BTreeSet<&str> = Default::default();
723
724 for modify in modlist.iter() {
725 match modify {
726 Modify::Present(a, v) if a == Attribute::Class.as_ref() => {
727 requested_pres_classes.extend(v.to_str())
728 }
729 Modify::Removed(a, v) if a == Attribute::Class.as_ref() => {
730 requested_rem_classes.extend(v.to_str())
731 }
732 Modify::Set(a, v) if a == Attribute::Class.as_ref() => {
733 if let Some(current_classes) = e.get_ava_as_iutf8(Attribute::Class) {
736 if let Some(requested_classes) = v.as_iutf8_set() {
737 requested_pres_classes.extend( requested_classes.difference(current_classes).map(|s| s.as_str()) );
742 requested_rem_classes.extend( current_classes.difference(requested_classes).map(|s| s.as_str()) );
743 } else {
744 error!("invalid valueset state - requested class set is not valid");
750 return false;
751 }
752 } else {
753 error!("invalid entry state - entry does not have attribute class and is not valid");
754 return false;
755 }
756 }
757 _ => {}
758 }
759 }
760
761 debug!(?requested_pres, "Requested present set");
762 debug!(?requested_rem, "Requested remove set");
763 debug!(?requested_pres_classes, "Requested present class set");
764 debug!(?requested_rem_classes, "Requested remove class set");
765 debug!(entry_id = %e.get_display_id());
766
767 let sync_agmts = self.get_sync_agreements();
768
769 match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
770 ModifyResult::Deny => false,
771 ModifyResult::Grant => true,
772 ModifyResult::Allow {
773 pres,
774 rem,
775 pres_cls,
776 rem_cls,
777 } => {
778 let mut decision = true;
779
780 if !requested_pres.is_subset(&pres) {
781 security_error!("requested_pres is not a subset of allowed");
782 security_error!(
783 "requested_pres: {:?} !⊆ allowed: {:?}",
784 requested_pres,
785 pres
786 );
787 decision = false
788 };
789
790 if !requested_rem.is_subset(&rem) {
791 security_error!("requested_rem is not a subset of allowed");
792 security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
793 decision = false;
794 };
795
796 if !requested_pres_classes.is_subset(&pres_cls) {
797 security_error!("requested_pres_classes is not a subset of allowed");
798 security_error!(
799 "requested_classes: {:?} !⊆ allowed: {:?}",
800 requested_pres_classes,
801 pres_cls
802 );
803 decision = false;
804 };
805
806 if !requested_rem_classes.is_subset(&rem_cls) {
807 security_error!("requested_rem_classes is not a subset of allowed");
808 security_error!(
809 "requested_classes: {:?} !⊆ allowed: {:?}",
810 requested_rem_classes,
811 rem_cls
812 );
813 decision = false;
814 }
815
816 if decision {
817 debug!("passed pres, rem, classes check.");
818 }
819
820 decision
822 }
823 }
824 });
825
826 if r {
827 debug!("allowed modify of {} entries ✅", entries.len());
828 } else {
829 security_access!("denied ❌ - modifications may not proceed");
830 }
831 Ok(r)
832 }
833
834 #[instrument(level = "debug", name = "access::create_allow_operation", skip_all)]
835 fn create_allow_operation(
836 &self,
837 ce: &CreateEvent,
838 entries: &[Entry<EntryInit, EntryNew>],
839 ) -> Result<bool, OperationError> {
840 let create_state = self.get_create();
842 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
843
844 let ident_memberof = ce.ident.get_memberof();
845
846 let related_acp: Vec<_> = create_state
848 .iter()
849 .filter_map(|acs| {
850 let (receiver_condition, target_condition) = resolve_access_conditions(
851 &ce.ident,
852 ident_memberof,
853 &acs.acp.receiver,
854 &acs.acp.target,
855 acp_resolve_filter_cache,
856 )?;
857
858 Some(AccessControlCreateResolved {
859 acp: acs,
860 receiver_condition,
861 target_condition,
862 })
863 })
864 .collect();
865
866 let decision = entries.iter().all(|e| {
868 let requested_pres: BTreeSet<_> = e.attr_keys().cloned().collect();
869 let Some(requested_pres_classes) = e
870 .get_ava_as_iutf8(Attribute::Class)
871 .map(|set| set.iter().map(|s| s.as_str()).collect::<BTreeSet<_>>())
872 else {
873 error!("unable to perform access control checks on entry with no classes, denied.");
874 return false;
875 };
876
877 debug!(?requested_pres, "Requested present set");
878 debug!(?requested_pres_classes, "Requested present class set");
879 debug!(entry_id = %e.get_display_id());
880
881 match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
882 CreateResult::Deny => false,
883 CreateResult::Grant => true,
884 CreateResult::Allow { pres, pres_cls } => {
885 let mut decision = true;
886
887 if !requested_pres.is_subset(&pres) {
888 security_error!("requested_pres is not a subset of allowed");
889 security_error!(
890 "requested_pres: {:?} !⊆ allowed: {:?}",
891 requested_pres,
892 pres
893 );
894 decision = false
895 };
896
897 if !requested_pres_classes.is_subset(&pres_cls) {
898 security_error!("requested_pres_classes is not a subset of allowed");
899 security_error!(
900 "requested_classes: {:?} !⊆ allowed: {:?}",
901 requested_pres_classes,
902 pres_cls
903 );
904 decision = false;
905 };
906
907 if decision {
908 debug!("passed pres, classes check.");
909 }
910
911 decision
912 }
913 }
914 });
915
916 if decision {
917 debug!("allowed create of {} entries ✅", entries.len());
918 } else {
919 security_access!("denied ❌ - create may not proceed");
920 }
921
922 Ok(decision)
923 }
924
925 #[instrument(level = "trace", name = "access::delete_related_acp", skip_all)]
926 fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
927 let delete_state = self.get_delete();
929 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
930
931 let ident_memberof = ident.get_memberof();
932
933 let related_acp: Vec<_> = delete_state
934 .iter()
935 .filter_map(|acs| {
936 let (receiver_condition, target_condition) = resolve_access_conditions(
937 ident,
938 ident_memberof,
939 &acs.acp.receiver,
940 &acs.acp.target,
941 acp_resolve_filter_cache,
942 )?;
943
944 Some(AccessControlDeleteResolved {
945 acp: acs,
946 receiver_condition,
947 target_condition,
948 })
949 })
950 .collect();
951
952 related_acp
953 }
954
955 #[instrument(level = "debug", name = "access::delete_allow_operation", skip_all)]
956 fn delete_allow_operation(
957 &self,
958 de: &DeleteEvent,
959 entries: &[Arc<EntrySealedCommitted>],
960 ) -> Result<bool, OperationError> {
961 let related_acp = self.delete_related_acp(&de.ident);
963
964 let r = entries.iter().all(|e| {
966 match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
967 DeleteResult::Deny => false,
968 DeleteResult::Grant => true,
969 }
970 });
971 if r {
972 debug!("allowed delete of {} entries ✅", entries.len());
973 } else {
974 security_access!("denied ❌ - delete may not proceed");
975 }
976 Ok(r)
977 }
978
979 #[instrument(level = "debug", name = "access::effective_permission_check", skip_all)]
980 fn effective_permission_check(
981 &self,
982 ident: &Identity,
983 attrs: Option<BTreeSet<Attribute>>,
984 entries: &[Arc<EntrySealedCommitted>],
985 ) -> Result<Vec<AccessEffectivePermission>, OperationError> {
986 trace!(ident = %ident, "Effective permission check");
995 let search_related_acp = self.search_related_acp(ident, attrs.as_ref());
1000 let modify_related_acp = self.modify_related_acp(ident);
1002 let delete_related_acp = self.delete_related_acp(ident);
1004
1005 let sync_agmts = self.get_sync_agreements();
1006
1007 let effective_permissions: Vec<_> = entries
1008 .iter()
1009 .map(|entry| {
1010 self.entry_effective_permission_check(
1011 ident,
1012 entry,
1013 &search_related_acp,
1014 &modify_related_acp,
1015 &delete_related_acp,
1016 sync_agmts,
1017 )
1018 })
1019 .collect();
1020
1021 effective_permissions.iter().for_each(|ep| {
1022 trace!(?ep);
1023 });
1024
1025 Ok(effective_permissions)
1026 }
1027
1028 fn entry_effective_permission_check<'b>(
1029 &'b self,
1030 ident: &Identity,
1031 entry: &Arc<EntrySealedCommitted>,
1032 search_related_acp: &[AccessControlSearchResolved<'b>],
1033 modify_related_acp: &[AccessControlModifyResolved<'b>],
1034 delete_related_acp: &[AccessControlDeleteResolved<'b>],
1035 sync_agmts: &HashMap<Uuid, BTreeSet<Attribute>>,
1036 ) -> AccessEffectivePermission {
1037 let search_effective = match apply_search_access(ident, search_related_acp, entry) {
1039 SearchResult::Deny => Access::Deny,
1040 SearchResult::Grant => Access::Grant,
1041 SearchResult::Allow(allowed_attrs) => {
1042 Access::Allow(allowed_attrs.into_iter().collect())
1044 }
1045 };
1046
1047 let (modify_pres, modify_rem, modify_pres_class, modify_rem_class) =
1049 match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
1050 ModifyResult::Deny => (
1051 Access::Deny,
1052 Access::Deny,
1053 AccessClass::Deny,
1054 AccessClass::Deny,
1055 ),
1056 ModifyResult::Grant => (
1057 Access::Grant,
1058 Access::Grant,
1059 AccessClass::Grant,
1060 AccessClass::Grant,
1061 ),
1062 ModifyResult::Allow {
1063 pres,
1064 rem,
1065 pres_cls,
1066 rem_cls,
1067 } => (
1068 Access::Allow(pres.into_iter().collect()),
1069 Access::Allow(rem.into_iter().collect()),
1070 AccessClass::Allow(pres_cls.into_iter().map(|s| s.into()).collect()),
1071 AccessClass::Allow(rem_cls.into_iter().map(|s| s.into()).collect()),
1072 ),
1073 };
1074
1075 let delete_status = apply_delete_access(ident, delete_related_acp, entry);
1077
1078 let delete = match delete_status {
1079 DeleteResult::Deny => false,
1080 DeleteResult::Grant => true,
1081 };
1082
1083 AccessEffectivePermission {
1084 ident: ident.get_uuid(),
1085 target: entry.get_uuid(),
1086 delete,
1087 search: search_effective,
1088 modify_pres,
1089 modify_rem,
1090 modify_pres_class,
1091 modify_rem_class,
1092 }
1093 }
1094}
1095
1096pub struct AccessControlsWriteTransaction<'a> {
1097 inner: CowCellWriteTxn<'a, AccessControlsInner>,
1098 acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
1099}
1100
1101impl AccessControlsWriteTransaction<'_> {
1102 pub fn update_search(
1106 &mut self,
1107 mut acps: Vec<AccessControlSearch>,
1108 ) -> Result<(), OperationError> {
1109 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_search);
1110 Ok(())
1111 }
1112
1113 pub fn update_create(
1114 &mut self,
1115 mut acps: Vec<AccessControlCreate>,
1116 ) -> Result<(), OperationError> {
1117 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_create);
1118 Ok(())
1119 }
1120
1121 pub fn update_modify(
1122 &mut self,
1123 mut acps: Vec<AccessControlModify>,
1124 ) -> Result<(), OperationError> {
1125 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_modify);
1126 Ok(())
1127 }
1128
1129 pub fn update_delete(
1130 &mut self,
1131 mut acps: Vec<AccessControlDelete>,
1132 ) -> Result<(), OperationError> {
1133 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_delete);
1134 Ok(())
1135 }
1136
1137 pub fn update_sync_agreements(
1138 &mut self,
1139 mut sync_agreements: HashMap<Uuid, BTreeSet<Attribute>>,
1140 ) {
1141 std::mem::swap(
1142 &mut sync_agreements,
1143 &mut self.inner.deref_mut().sync_agreements,
1144 );
1145 }
1146
1147 pub fn commit(self) -> Result<(), OperationError> {
1148 self.inner.commit();
1149
1150 Ok(())
1151 }
1152}
1153
1154impl<'a> AccessControlsTransaction<'a> for AccessControlsWriteTransaction<'a> {
1155 fn get_search(&self) -> &Vec<AccessControlSearch> {
1156 &self.inner.acps_search
1157 }
1158
1159 fn get_create(&self) -> &Vec<AccessControlCreate> {
1160 &self.inner.acps_create
1161 }
1162
1163 fn get_modify(&self) -> &Vec<AccessControlModify> {
1164 &self.inner.acps_modify
1165 }
1166
1167 fn get_delete(&self) -> &Vec<AccessControlDelete> {
1168 &self.inner.acps_delete
1169 }
1170
1171 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
1172 &self.inner.sync_agreements
1173 }
1174
1175 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
1176 unsafe {
1177 let mptr = self.acp_resolve_filter_cache.as_ptr();
1178 &mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
1179 }
1180 }
1181}
1182
1183pub struct AccessControlsReadTransaction<'a> {
1188 inner: CowCellReadTxn<AccessControlsInner>,
1189 acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
1191}
1192
1193unsafe impl Sync for AccessControlsReadTransaction<'_> {}
1194
1195unsafe impl Send for AccessControlsReadTransaction<'_> {}
1196
1197impl<'a> AccessControlsTransaction<'a> for AccessControlsReadTransaction<'a> {
1198 fn get_search(&self) -> &Vec<AccessControlSearch> {
1199 &self.inner.acps_search
1200 }
1201
1202 fn get_create(&self) -> &Vec<AccessControlCreate> {
1203 &self.inner.acps_create
1204 }
1205
1206 fn get_modify(&self) -> &Vec<AccessControlModify> {
1207 &self.inner.acps_modify
1208 }
1209
1210 fn get_delete(&self) -> &Vec<AccessControlDelete> {
1211 &self.inner.acps_delete
1212 }
1213
1214 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
1215 &self.inner.sync_agreements
1216 }
1217
1218 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
1219 unsafe {
1220 let mptr = self.acp_resolve_filter_cache.as_ptr();
1221 &mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
1222 }
1223 }
1224}
1225
1226impl Default for AccessControls {
1231 #![allow(clippy::expect_used)]
1232 fn default() -> Self {
1233 AccessControls {
1234 inner: CowCell::new(AccessControlsInner {
1235 acps_search: Vec::with_capacity(0),
1236 acps_create: Vec::with_capacity(0),
1237 acps_modify: Vec::with_capacity(0),
1238 acps_delete: Vec::with_capacity(0),
1239 sync_agreements: HashMap::default(),
1240 }),
1241 acp_resolve_filter_cache: ARCacheBuilder::new()
1244 .set_size(ACP_RESOLVE_FILTER_CACHE_MAX, ACP_RESOLVE_FILTER_CACHE_LOCAL)
1245 .set_reader_quiesce(true)
1246 .build()
1247 .expect("Failed to construct acp_resolve_filter_cache"),
1248 }
1249 }
1250}
1251
1252impl AccessControls {
1253 pub fn try_quiesce(&self) {
1254 self.acp_resolve_filter_cache.try_quiesce();
1255 }
1256
1257 pub fn read(&self) -> AccessControlsReadTransaction<'_> {
1258 AccessControlsReadTransaction {
1259 inner: self.inner.read(),
1260 acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
1262 }
1263 }
1264
1265 pub fn write(&self) -> AccessControlsWriteTransaction<'_> {
1266 AccessControlsWriteTransaction {
1267 inner: self.inner.write(),
1268 acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
1271 }
1272 }
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277 use hashbrown::HashMap;
1278 use std::collections::BTreeSet;
1279 use std::sync::Arc;
1280
1281 use uuid::uuid;
1282
1283 use super::{
1284 profiles::{
1285 AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlProfile,
1286 AccessControlSearch, AccessControlTarget,
1287 },
1288 Access, AccessClass, AccessControls, AccessControlsTransaction, AccessEffectivePermission,
1289 };
1290 use crate::migration_data::BUILTIN_ACCOUNT_ANONYMOUS;
1291 use crate::prelude::*;
1292 use crate::valueset::ValueSetIname;
1293
1294 const UUID_TEST_ACCOUNT_1: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
1295 const UUID_TEST_ACCOUNT_2: Uuid = uuid::uuid!("cec0852a-abdf-4ea6-9dae-d3157cb33d3a");
1296 const UUID_TEST_GROUP_1: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
1297 const UUID_TEST_GROUP_2: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
1298
1299 pub static E_TEST_ACCOUNT_1: LazyLock<Arc<EntrySealedCommitted>> = LazyLock::new(|| {
1300 Arc::new(
1301 entry_init!(
1302 (Attribute::Class, EntryClass::Object.to_value()),
1303 (Attribute::Name, Value::new_iname("test_account_1")),
1304 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
1305 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1))
1306 )
1307 .into_sealed_committed(),
1308 )
1309 });
1310 pub static E_TEST_ACCOUNT_2: LazyLock<Arc<EntrySealedCommitted>> = LazyLock::new(|| {
1311 Arc::new(
1312 entry_init!(
1313 (Attribute::Class, EntryClass::Object.to_value()),
1314 (Attribute::Name, Value::new_iname("test_account_1")),
1315 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_2)),
1316 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_2))
1317 )
1318 .into_sealed_committed(),
1319 )
1320 });
1321
1322 macro_rules! acp_from_entry_err {
1323 (
1324 $qs:expr,
1325 $e:expr,
1326 $type:ty
1327 ) => {{
1328 let ev1 = $e.into_sealed_committed();
1329
1330 let r1 = <$type>::try_from($qs, &ev1);
1331 error!(?r1);
1332 assert!(r1.is_err());
1333 }};
1334 }
1335
1336 macro_rules! acp_from_entry_ok {
1337 (
1338 $qs:expr,
1339 $e:expr,
1340 $type:ty
1341 ) => {{
1342 let ev1 = $e.into_sealed_committed();
1343
1344 let r1 = <$type>::try_from($qs, &ev1);
1345 assert!(r1.is_ok());
1346 r1.unwrap()
1347 }};
1348 }
1349
1350 #[qs_test]
1351 async fn test_access_acp_parser(qs: &QueryServer) {
1352 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1361
1362 acp_from_entry_err!(
1363 &mut qs_write,
1364 entry_init!(
1365 (Attribute::Class, EntryClass::Object.to_value()),
1366 (Attribute::Name, Value::new_iname("acp_invalid")),
1367 (
1368 Attribute::Uuid,
1369 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1370 )
1371 ),
1372 AccessControlProfile
1373 );
1374
1375 acp_from_entry_err!(
1376 &mut qs_write,
1377 entry_init!(
1378 (Attribute::Class, EntryClass::Object.to_value()),
1379 (
1380 Attribute::Class,
1381 EntryClass::AccessControlProfile.to_value()
1382 ),
1383 (
1384 Attribute::Class,
1385 EntryClass::AccessControlReceiverGroup.to_value()
1386 ),
1387 (
1388 Attribute::Class,
1389 EntryClass::AccessControlTargetScope.to_value()
1390 ),
1391 (Attribute::Name, Value::new_iname("acp_invalid")),
1392 (
1393 Attribute::Uuid,
1394 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1395 )
1396 ),
1397 AccessControlProfile
1398 );
1399
1400 acp_from_entry_err!(
1401 &mut qs_write,
1402 entry_init!(
1403 (Attribute::Class, EntryClass::Object.to_value()),
1404 (
1405 Attribute::Class,
1406 EntryClass::AccessControlProfile.to_value()
1407 ),
1408 (
1409 Attribute::Class,
1410 EntryClass::AccessControlReceiverGroup.to_value()
1411 ),
1412 (
1413 Attribute::Class,
1414 EntryClass::AccessControlTargetScope.to_value()
1415 ),
1416 (Attribute::Name, Value::new_iname("acp_invalid")),
1417 (
1418 Attribute::Uuid,
1419 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1420 ),
1421 (Attribute::AcpReceiverGroup, Value::Bool(true)),
1422 (Attribute::AcpTargetScope, Value::Bool(true))
1423 ),
1424 AccessControlProfile
1425 );
1426
1427 acp_from_entry_ok!(
1429 &mut qs_write,
1430 entry_init!(
1431 (Attribute::Class, EntryClass::Object.to_value()),
1432 (
1433 Attribute::Class,
1434 EntryClass::AccessControlProfile.to_value()
1435 ),
1436 (
1437 Attribute::Class,
1438 EntryClass::AccessControlReceiverGroup.to_value()
1439 ),
1440 (
1441 Attribute::Class,
1442 EntryClass::AccessControlTargetScope.to_value()
1443 ),
1444 (Attribute::Name, Value::new_iname("acp_valid")),
1445 (
1446 Attribute::Uuid,
1447 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1448 ),
1449 (
1450 Attribute::AcpReceiverGroup,
1451 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1452 ),
1453 (
1454 Attribute::AcpTargetScope,
1455 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1456 )
1457 ),
1458 AccessControlProfile
1459 );
1460 }
1461
1462 #[qs_test]
1463 async fn test_access_acp_delete_parser(qs: &QueryServer) {
1464 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1465
1466 acp_from_entry_err!(
1467 &mut qs_write,
1468 entry_init!(
1469 (Attribute::Class, EntryClass::Object.to_value()),
1470 (
1471 Attribute::Class,
1472 EntryClass::AccessControlProfile.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 AccessControlDelete
1489 );
1490
1491 acp_from_entry_ok!(
1492 &mut qs_write,
1493 entry_init!(
1494 (Attribute::Class, EntryClass::Object.to_value()),
1495 (
1496 Attribute::Class,
1497 EntryClass::AccessControlProfile.to_value()
1498 ),
1499 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
1500 (Attribute::Name, Value::new_iname("acp_valid")),
1501 (
1502 Attribute::Uuid,
1503 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1504 ),
1505 (
1506 Attribute::AcpReceiverGroup,
1507 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1508 ),
1509 (
1510 Attribute::AcpTargetScope,
1511 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1512 )
1513 ),
1514 AccessControlDelete
1515 );
1516 }
1517
1518 #[qs_test]
1519 async fn test_access_acp_search_parser(qs: &QueryServer) {
1520 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1522
1523 acp_from_entry_err!(
1525 &mut qs_write,
1526 entry_init!(
1527 (Attribute::Class, EntryClass::Object.to_value()),
1528 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1529 (Attribute::Name, Value::new_iname("acp_valid")),
1530 (
1531 Attribute::Uuid,
1532 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1533 ),
1534 (
1535 Attribute::AcpReceiverGroup,
1536 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1537 ),
1538 (
1539 Attribute::AcpTargetScope,
1540 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1541 ),
1542 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1543 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1544 ),
1545 AccessControlSearch
1546 );
1547
1548 acp_from_entry_err!(
1550 &mut qs_write,
1551 entry_init!(
1552 (Attribute::Class, EntryClass::Object.to_value()),
1553 (
1554 Attribute::Class,
1555 EntryClass::AccessControlProfile.to_value()
1556 ),
1557 (Attribute::Name, Value::new_iname("acp_valid")),
1558 (
1559 Attribute::Uuid,
1560 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1561 ),
1562 (
1563 Attribute::AcpReceiverGroup,
1564 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1565 ),
1566 (
1567 Attribute::AcpTargetScope,
1568 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1569 ),
1570 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1571 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1572 ),
1573 AccessControlSearch
1574 );
1575
1576 acp_from_entry_err!(
1578 &mut qs_write,
1579 entry_init!(
1580 (Attribute::Class, EntryClass::Object.to_value()),
1581 (
1582 Attribute::Class,
1583 EntryClass::AccessControlProfile.to_value()
1584 ),
1585 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1586 (Attribute::Name, Value::new_iname("acp_valid")),
1587 (
1588 Attribute::Uuid,
1589 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1590 ),
1591 (
1592 Attribute::AcpReceiverGroup,
1593 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1594 ),
1595 (
1596 Attribute::AcpTargetScope,
1597 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1598 )
1599 ),
1600 AccessControlSearch
1601 );
1602
1603 acp_from_entry_ok!(
1605 &mut qs_write,
1606 entry_init!(
1607 (Attribute::Class, EntryClass::Object.to_value()),
1608 (
1609 Attribute::Class,
1610 EntryClass::AccessControlProfile.to_value()
1611 ),
1612 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1613 (Attribute::Name, Value::new_iname("acp_valid")),
1614 (
1615 Attribute::Uuid,
1616 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1617 ),
1618 (
1619 Attribute::AcpReceiverGroup,
1620 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1621 ),
1622 (
1623 Attribute::AcpTargetScope,
1624 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1625 ),
1626 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1627 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1628 ),
1629 AccessControlSearch
1630 );
1631 }
1632
1633 #[qs_test]
1634 async fn test_access_acp_modify_parser(qs: &QueryServer) {
1635 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1637
1638 acp_from_entry_err!(
1639 &mut qs_write,
1640 entry_init!(
1641 (Attribute::Class, EntryClass::Object.to_value()),
1642 (
1643 Attribute::Class,
1644 EntryClass::AccessControlProfile.to_value()
1645 ),
1646 (Attribute::Name, Value::new_iname("acp_invalid")),
1647 (
1648 Attribute::Uuid,
1649 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1650 ),
1651 (
1652 Attribute::AcpReceiverGroup,
1653 Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1654 ),
1655 (
1656 Attribute::AcpTargetScope,
1657 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1658 )
1659 ),
1660 AccessControlModify
1661 );
1662
1663 acp_from_entry_ok!(
1664 &mut qs_write,
1665 entry_init!(
1666 (Attribute::Class, EntryClass::Object.to_value()),
1667 (
1668 Attribute::Class,
1669 EntryClass::AccessControlProfile.to_value()
1670 ),
1671 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1672 (Attribute::Name, Value::new_iname("acp_valid")),
1673 (
1674 Attribute::Uuid,
1675 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1676 ),
1677 (
1678 Attribute::AcpReceiverGroup,
1679 Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1680 ),
1681 (
1682 Attribute::AcpTargetScope,
1683 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1684 )
1685 ),
1686 AccessControlModify
1687 );
1688
1689 acp_from_entry_ok!(
1690 &mut qs_write,
1691 entry_init!(
1692 (Attribute::Class, EntryClass::Object.to_value()),
1693 (
1694 Attribute::Class,
1695 EntryClass::AccessControlProfile.to_value()
1696 ),
1697 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1698 (Attribute::Name, Value::new_iname("acp_valid")),
1699 (
1700 Attribute::Uuid,
1701 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1702 ),
1703 (
1704 Attribute::AcpReceiverGroup,
1705 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1706 ),
1707 (
1708 Attribute::AcpTargetScope,
1709 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1710 ),
1711 (
1712 Attribute::AcpModifyRemovedAttr,
1713 Value::from(Attribute::Name)
1714 ),
1715 (
1716 Attribute::AcpModifyPresentAttr,
1717 Value::from(Attribute::Name)
1718 ),
1719 (Attribute::AcpModifyClass, EntryClass::Object.to_value())
1720 ),
1721 AccessControlModify
1722 );
1723 }
1724
1725 #[qs_test]
1726 async fn test_access_acp_create_parser(qs: &QueryServer) {
1727 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1729
1730 acp_from_entry_err!(
1731 &mut qs_write,
1732 entry_init!(
1733 (Attribute::Class, EntryClass::Object.to_value()),
1734 (
1735 Attribute::Class,
1736 EntryClass::AccessControlProfile.to_value()
1737 ),
1738 (Attribute::Name, Value::new_iname("acp_invalid")),
1739 (
1740 Attribute::Uuid,
1741 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1742 ),
1743 (
1744 Attribute::AcpReceiverGroup,
1745 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1746 ),
1747 (
1748 Attribute::AcpTargetScope,
1749 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1750 ),
1751 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1752 (Attribute::AcpCreateClass, EntryClass::Object.to_value())
1753 ),
1754 AccessControlCreate
1755 );
1756
1757 acp_from_entry_ok!(
1758 &mut qs_write,
1759 entry_init!(
1760 (Attribute::Class, EntryClass::Object.to_value()),
1761 (
1762 Attribute::Class,
1763 EntryClass::AccessControlProfile.to_value()
1764 ),
1765 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1766 (Attribute::Name, Value::new_iname("acp_valid")),
1767 (
1768 Attribute::Uuid,
1769 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1770 ),
1771 (
1772 Attribute::AcpReceiverGroup,
1773 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1774 ),
1775 (
1776 Attribute::AcpTargetScope,
1777 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1778 )
1779 ),
1780 AccessControlCreate
1781 );
1782
1783 acp_from_entry_ok!(
1784 &mut qs_write,
1785 entry_init!(
1786 (Attribute::Class, EntryClass::Object.to_value()),
1787 (
1788 Attribute::Class,
1789 EntryClass::AccessControlProfile.to_value()
1790 ),
1791 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1792 (Attribute::Name, Value::new_iname("acp_valid")),
1793 (
1794 Attribute::Uuid,
1795 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1796 ),
1797 (
1798 Attribute::AcpReceiverGroup,
1799 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1800 ),
1801 (
1802 Attribute::AcpTargetScope,
1803 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1804 ),
1805 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1806 (Attribute::AcpCreateClass, EntryClass::Object.to_value())
1807 ),
1808 AccessControlCreate
1809 );
1810 }
1811
1812 #[qs_test]
1813 async fn test_access_acp_compound_parser(qs: &QueryServer) {
1814 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1819
1820 let e = entry_init!(
1821 (Attribute::Class, EntryClass::Object.to_value()),
1822 (
1823 Attribute::Class,
1824 EntryClass::AccessControlProfile.to_value()
1825 ),
1826 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1827 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
1828 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1829 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1830 (Attribute::Name, Value::new_iname("acp_valid")),
1831 (
1832 Attribute::Uuid,
1833 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1834 ),
1835 (
1836 Attribute::AcpReceiverGroup,
1837 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1838 ),
1839 (
1840 Attribute::AcpTargetScope,
1841 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1842 ),
1843 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1844 (Attribute::AcpCreateClass, EntryClass::Class.to_value()),
1845 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1846 (
1847 Attribute::AcpModifyRemovedAttr,
1848 Value::from(Attribute::Name)
1849 ),
1850 (
1851 Attribute::AcpModifyPresentAttr,
1852 Value::from(Attribute::Name)
1853 ),
1854 (Attribute::AcpModifyClass, EntryClass::Object.to_value())
1855 );
1856
1857 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlCreate);
1858 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlDelete);
1859 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlModify);
1860 acp_from_entry_ok!(&mut qs_write, e, AccessControlSearch);
1861 }
1862
1863 macro_rules! test_acp_search {
1864 (
1865 $se:expr,
1866 $controls:expr,
1867 $entries:expr,
1868 $expect:expr
1869 ) => {{
1870 let ac = AccessControls::default();
1871 let mut acw = ac.write();
1872 acw.update_search($controls).expect("Failed to update");
1873 let acw = acw;
1874
1875 let res = acw
1876 .search_filter_entries(&mut $se, $entries)
1877 .expect("op failed");
1878 debug!("result --> {:?}", res);
1879 debug!("expect --> {:?}", $expect);
1880 assert_eq!(res, $expect);
1882 }};
1883 }
1884
1885 macro_rules! test_acp_search_reduce {
1886 (
1887 $se:expr,
1888 $controls:expr,
1889 $entries:expr,
1890 $expect:expr
1891 ) => {{
1892 let ac = AccessControls::default();
1893 let mut acw = ac.write();
1894 acw.update_search($controls).expect("Failed to update");
1895 let acw = acw;
1896
1897 let res = acw
1899 .search_filter_entries(&mut $se, $entries)
1900 .expect("operation failed");
1901 let reduced = acw
1903 .search_filter_entry_attributes(&mut $se, res)
1904 .expect("operation failed");
1905
1906 let expect_set: Vec<Entry<EntryReduced, EntryCommitted>> =
1908 $expect.into_iter().map(|e| e.into_reduced()).collect();
1909
1910 debug!("expect --> {:?}", expect_set);
1911 debug!("result --> {:?}", reduced);
1912 assert_eq!(reduced, expect_set);
1914 }};
1915 }
1916
1917 #[test]
1918 fn test_access_internal_search() {
1919 let se = SearchEvent::new_internal_invalid(filter!(f_pres(Attribute::Class)));
1921
1922 let expect = vec![E_TEST_ACCOUNT_1.clone()];
1923 let entries = vec![E_TEST_ACCOUNT_1.clone()];
1924
1925 test_acp_search!(
1927 &se,
1928 vec![AccessControlSearch::from_raw(
1929 "test_acp",
1930 Uuid::new_v4(),
1931 UUID_TEST_GROUP_1,
1932 filter_valid!(f_pres(Attribute::NonExist)), Attribute::Name.as_ref(), )],
1935 entries,
1936 expect
1937 );
1938 }
1939
1940 #[test]
1941 fn test_access_enforce_search() {
1942 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
1944 let ev2 = E_TESTPERSON_2.clone().into_sealed_committed();
1945
1946 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
1947
1948 let se_a = SearchEvent::new_impersonate_entry(
1949 E_TEST_ACCOUNT_1.clone(),
1950 filter_all!(f_pres(Attribute::Name)),
1951 );
1952 let ex_a = vec![Arc::new(ev1)];
1953
1954 let se_b = SearchEvent::new_impersonate_entry(
1955 E_TEST_ACCOUNT_2.clone(),
1956 filter_all!(f_pres(Attribute::Name)),
1957 );
1958 let ex_b = vec![];
1959
1960 let acp = AccessControlSearch::from_raw(
1961 "test_acp",
1962 Uuid::new_v4(),
1963 UUID_TEST_GROUP_1,
1965 filter_valid!(f_eq(
1967 Attribute::Name,
1968 PartialValue::new_iname("testperson1")
1969 )),
1970 Attribute::Name.as_ref(),
1973 );
1974
1975 test_acp_search!(&se_a, vec![acp.clone()], r_set.clone(), ex_a);
1977
1978 test_acp_search!(&se_b, vec![acp], r_set, ex_b);
1980 }
1981
1982 #[test]
1983 fn test_access_enforce_scope_search() {
1984 sketching::test_init();
1985 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
1987
1988 let ex_some = vec![Arc::new(ev1.clone())];
1989
1990 let r_set = vec![Arc::new(ev1)];
1991
1992 let se_ro = SearchEvent::new_impersonate_identity(
1993 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
1994 filter_all!(f_pres(Attribute::Name)),
1995 );
1996
1997 let se_rw = SearchEvent::new_impersonate_identity(
1998 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
1999 filter_all!(f_pres(Attribute::Name)),
2000 );
2001
2002 let acp = AccessControlSearch::from_raw(
2003 "test_acp",
2004 Uuid::new_v4(),
2005 UUID_TEST_GROUP_1,
2007 filter_valid!(f_eq(
2009 Attribute::Name,
2010 PartialValue::new_iname("testperson1")
2011 )),
2012 Attribute::Name.as_ref(),
2015 );
2016
2017 test_acp_search!(&se_ro, vec![acp.clone()], r_set.clone(), ex_some);
2019
2020 test_acp_search!(&se_rw, vec![acp], r_set, ex_some);
2021 }
2022
2023 #[test]
2024 fn test_access_enforce_scope_search_attrs() {
2025 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2029 let r_set = vec![Arc::new(ev1)];
2030
2031 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2032
2033 let ex_anon_some = vec![exv1];
2034
2035 let se_anon_ro = SearchEvent::new_impersonate_identity(
2036 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2037 filter_all!(f_pres(Attribute::Name)),
2038 );
2039
2040 let acp = AccessControlSearch::from_raw(
2041 "test_acp",
2042 Uuid::new_v4(),
2043 UUID_TEST_GROUP_1,
2045 filter_valid!(f_eq(
2047 Attribute::Name,
2048 PartialValue::new_iname("testperson1")
2049 )),
2050 Attribute::Name.as_ref(),
2053 );
2054
2055 test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
2057 }
2058
2059 pub static E_TESTPERSON_1_REDUCED: LazyLock<EntryInitNew> =
2060 LazyLock::new(|| entry_init_fn([(Attribute::Name, Value::new_iname("testperson1"))]));
2061
2062 #[test]
2063 fn test_access_enforce_search_attrs() {
2064 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2068 let r_set = vec![Arc::new(ev1)];
2069
2070 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2071 let ex_anon = vec![exv1];
2072
2073 let se_anon = SearchEvent::new_impersonate_entry(
2074 E_TEST_ACCOUNT_1.clone(),
2075 filter_all!(f_eq(
2076 Attribute::Name,
2077 PartialValue::new_iname("testperson1")
2078 )),
2079 );
2080
2081 let acp = AccessControlSearch::from_raw(
2082 "test_acp",
2083 Uuid::new_v4(),
2084 UUID_TEST_GROUP_1,
2086 filter_valid!(f_eq(
2088 Attribute::Name,
2089 PartialValue::new_iname("testperson1")
2090 )),
2091 Attribute::Name.as_ref(),
2094 );
2095
2096 test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
2098 }
2099
2100 #[test]
2101 fn test_access_enforce_search_attrs_req() {
2102 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2106
2107 let r_set = vec![Arc::new(ev1)];
2108
2109 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2110 let ex_anon = vec![exv1];
2111
2112 let mut se_anon = SearchEvent::new_impersonate_entry(
2113 E_TEST_ACCOUNT_1.clone(),
2114 filter_all!(f_eq(
2115 Attribute::Name,
2116 PartialValue::new_iname("testperson1")
2117 )),
2118 );
2119 se_anon.attrs = Some(btreeset![Attribute::Name]);
2121
2122 let acp = AccessControlSearch::from_raw(
2123 "test_acp",
2124 Uuid::new_v4(),
2125 UUID_TEST_GROUP_1,
2127 filter_valid!(f_eq(
2129 Attribute::Name,
2130 PartialValue::new_iname("testperson1")
2131 )),
2132 "name uuid",
2135 );
2136
2137 test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
2139 }
2140
2141 macro_rules! test_acp_modify {
2142 (
2143 $me:expr,
2144 $controls:expr,
2145 $entries:expr,
2146 $expect:expr
2147 ) => {{
2148 let ac = AccessControls::default();
2149 let mut acw = ac.write();
2150 acw.update_modify($controls).expect("Failed to update");
2151 let acw = acw;
2152
2153 let res = acw
2154 .modify_allow_operation(&mut $me, $entries)
2155 .expect("op failed");
2156
2157 debug!("result --> {:?}", res);
2158 debug!("expect --> {:?}", $expect);
2159 assert_eq!($expect, res);
2161 }};
2162 (
2163 $me:expr,
2164 $controls:expr,
2165 $sync_uuid:expr,
2166 $sync_yield_attr:expr,
2167 $entries:expr,
2168 $expect:expr
2169 ) => {{
2170 let ac = AccessControls::default();
2171 let mut acw = ac.write();
2172 acw.update_modify($controls).expect("Failed to update");
2173 let mut sync_agmt = HashMap::new();
2174 let mut set = BTreeSet::new();
2175 set.insert($sync_yield_attr);
2176 sync_agmt.insert($sync_uuid, set);
2177 acw.update_sync_agreements(sync_agmt);
2178 let acw = acw;
2179
2180 let res = acw
2181 .modify_allow_operation(&mut $me, $entries)
2182 .expect("op failed");
2183
2184 debug!("result --> {:?}", res);
2185 debug!("expect --> {:?}", $expect);
2186 assert_eq!($expect, res);
2188 }};
2189 }
2190
2191 #[test]
2192 fn test_access_enforce_modify() {
2193 sketching::test_init();
2194
2195 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2196 let r_set = vec![Arc::new(ev1)];
2197
2198 let me_pres = ModifyEvent::new_impersonate_entry(
2200 E_TEST_ACCOUNT_1.clone(),
2201 filter_all!(f_eq(
2202 Attribute::Name,
2203 PartialValue::new_iname("testperson1")
2204 )),
2205 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2206 );
2207 let me_rem = ModifyEvent::new_impersonate_entry(
2209 E_TEST_ACCOUNT_1.clone(),
2210 filter_all!(f_eq(
2211 Attribute::Name,
2212 PartialValue::new_iname("testperson1")
2213 )),
2214 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
2215 );
2216 let me_purge = ModifyEvent::new_impersonate_entry(
2218 E_TEST_ACCOUNT_1.clone(),
2219 filter_all!(f_eq(
2220 Attribute::Name,
2221 PartialValue::new_iname("testperson1")
2222 )),
2223 modlist!([m_purge(Attribute::Name)]),
2224 );
2225
2226 let me_set = ModifyEvent::new_impersonate_entry(
2228 E_TEST_ACCOUNT_1.clone(),
2229 filter_all!(f_eq(
2230 Attribute::Name,
2231 PartialValue::new_iname("testperson1")
2232 )),
2233 modlist!([Modify::Set(Attribute::Name, ValueSetIname::new("value"))]),
2234 );
2235
2236 let me_pres_class = ModifyEvent::new_impersonate_entry(
2238 E_TEST_ACCOUNT_1.clone(),
2239 filter_all!(f_eq(
2240 Attribute::Name,
2241 PartialValue::new_iname("testperson1")
2242 )),
2243 modlist!([m_pres(Attribute::Class, &EntryClass::Account.to_value())]),
2244 );
2245 let me_rem_class = ModifyEvent::new_impersonate_entry(
2247 E_TEST_ACCOUNT_1.clone(),
2248 filter_all!(f_eq(
2249 Attribute::Name,
2250 PartialValue::new_iname("testperson1")
2251 )),
2252 modlist!([m_remove(
2253 Attribute::Class,
2254 &EntryClass::Account.to_partialvalue()
2255 )]),
2256 );
2257 let me_purge_class = ModifyEvent::new_impersonate_entry(
2259 E_TEST_ACCOUNT_1.clone(),
2260 filter_all!(f_eq(
2261 Attribute::Name,
2262 PartialValue::new_iname("testperson1")
2263 )),
2264 modlist!([m_purge(Attribute::Class)]),
2265 );
2266
2267 let me_set_class = ModifyEvent::new_impersonate_entry(
2269 E_TEST_ACCOUNT_1.clone(),
2270 filter_all!(f_eq(
2271 Attribute::Name,
2272 PartialValue::new_iname("testperson1")
2273 )),
2274 modlist!([Modify::Set(
2275 Attribute::Class,
2276 EntryClass::Account.to_valueset()
2277 )]),
2278 );
2279
2280 let acp_allow = AccessControlModify::from_raw(
2282 "test_modify_allow",
2283 Uuid::new_v4(),
2284 UUID_TEST_GROUP_1,
2286 filter_valid!(f_eq(
2288 Attribute::Name,
2289 PartialValue::new_iname("testperson1")
2290 )),
2291 "name class",
2293 "name class",
2295 EntryClass::Account.into(),
2297 EntryClass::Account.into(),
2299 );
2300 let acp_deny = AccessControlModify::from_raw(
2302 "test_modify_deny",
2303 Uuid::new_v4(),
2304 UUID_TEST_GROUP_1,
2306 filter_valid!(f_eq(
2308 Attribute::Name,
2309 PartialValue::new_iname("testperson1")
2310 )),
2311 "member class",
2313 "member class",
2315 EntryClass::Group.into(),
2316 EntryClass::Group.into(),
2317 );
2318 let acp_no_class = AccessControlModify::from_raw(
2320 "test_modify_no_class",
2321 Uuid::new_v4(),
2322 UUID_TEST_GROUP_1,
2324 filter_valid!(f_eq(
2326 Attribute::Name,
2327 PartialValue::new_iname("testperson1")
2328 )),
2329 "name class",
2331 "name class",
2333 EntryClass::Group.into(),
2335 EntryClass::Group.into(),
2336 );
2337
2338 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r_set, true);
2340 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r_set, true);
2342 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r_set, true);
2344 test_acp_modify!(&me_set, vec![acp_allow.clone()], &r_set, true);
2346
2347 test_acp_modify!(&me_pres, vec![acp_deny.clone()], &r_set, false);
2349 test_acp_modify!(&me_rem, vec![acp_deny.clone()], &r_set, false);
2351 test_acp_modify!(&me_purge, vec![acp_deny.clone()], &r_set, false);
2353 test_acp_modify!(&me_set, vec![acp_deny.clone()], &r_set, false);
2355
2356 test_acp_modify!(&me_pres_class, vec![acp_allow.clone()], &r_set, true);
2358 test_acp_modify!(&me_rem_class, vec![acp_allow.clone()], &r_set, true);
2360 test_acp_modify!(&me_purge_class, vec![acp_allow.clone()], &r_set, false);
2362 test_acp_modify!(&me_set_class, vec![acp_allow], &r_set, true);
2364
2365 test_acp_modify!(&me_pres_class, vec![acp_no_class.clone()], &r_set, false);
2367 test_acp_modify!(&me_pres_class, vec![acp_deny.clone()], &r_set, false);
2369 test_acp_modify!(&me_rem_class, vec![acp_no_class.clone()], &r_set, false);
2371 test_acp_modify!(&me_rem_class, vec![acp_deny.clone()], &r_set, false);
2373
2374 test_acp_modify!(&me_set_class, vec![acp_no_class], &r_set, false);
2376 test_acp_modify!(&me_set_class, vec![acp_deny], &r_set, false);
2378 }
2379
2380 #[test]
2381 fn test_access_enforce_scope_modify() {
2382 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2383 let r_set = vec![Arc::new(ev1)];
2384
2385 let me_pres_ro = ModifyEvent::new_impersonate_identity(
2387 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2388 filter_all!(f_eq(
2389 Attribute::Name,
2390 PartialValue::new_iname("testperson1")
2391 )),
2392 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2393 );
2394
2395 let me_pres_rw = ModifyEvent::new_impersonate_identity(
2397 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2398 filter_all!(f_eq(
2399 Attribute::Name,
2400 PartialValue::new_iname("testperson1")
2401 )),
2402 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2403 );
2404
2405 let acp_allow = AccessControlModify::from_raw(
2406 "test_modify_allow",
2407 Uuid::new_v4(),
2408 UUID_TEST_GROUP_1,
2410 filter_valid!(f_eq(
2412 Attribute::Name,
2413 PartialValue::new_iname("testperson1")
2414 )),
2415 "name class",
2417 "name class",
2419 EntryClass::Account.into(),
2421 EntryClass::Account.into(),
2422 );
2423
2424 test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
2425
2426 test_acp_modify!(&me_pres_rw, vec![acp_allow], &r_set, true);
2427 }
2428
2429 macro_rules! test_acp_create {
2430 (
2431 $ce:expr,
2432 $controls:expr,
2433 $entries:expr,
2434 $expect:expr
2435 ) => {{
2436 let ac = AccessControls::default();
2437 let mut acw = ac.write();
2438 acw.update_create($controls).expect("Failed to update");
2439 let acw = acw;
2440
2441 let res = acw
2442 .create_allow_operation(&mut $ce, $entries)
2443 .expect("op failed");
2444
2445 debug!("result --> {:?}", res);
2446 debug!("expect --> {:?}", $expect);
2447 assert_eq!(res, $expect);
2449 }};
2450 }
2451
2452 #[test]
2453 fn test_access_enforce_create() {
2454 let ev1 = entry_init!(
2455 (Attribute::Class, EntryClass::Account.to_value()),
2456 (Attribute::Name, Value::new_iname("testperson1")),
2457 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2458 );
2459 let r1_set = vec![ev1];
2460
2461 let ev2 = entry_init!(
2462 (Attribute::Class, EntryClass::Account.to_value()),
2463 (Attribute::TestNotAllowed, Value::new_iutf8("notallowed")),
2464 (Attribute::Name, Value::new_iname("testperson1")),
2465 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2466 );
2467
2468 let r2_set = vec![ev2];
2469
2470 let ev3 = entry_init!(
2471 (Attribute::Class, EntryClass::Account.to_value()),
2472 (Attribute::Class, Value::new_iutf8("notallowed")),
2473 (Attribute::Name, Value::new_iname("testperson1")),
2474 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2475 );
2476 let r3_set = vec![ev3];
2477
2478 let ev4 = entry_init!(
2479 (Attribute::Class, EntryClass::Account.to_value()),
2480 (Attribute::Class, EntryClass::Group.to_value()),
2481 (Attribute::Name, Value::new_iname("testperson1")),
2482 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2483 );
2484 let r4_set = vec![ev4];
2485
2486 let ce_admin = CreateEvent::new_impersonate_identity(
2493 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2494 vec![],
2495 );
2496
2497 let acp = AccessControlCreate::from_raw(
2498 "test_create",
2499 Uuid::new_v4(),
2500 UUID_TEST_GROUP_1,
2502 filter_valid!(f_eq(
2505 Attribute::Name,
2506 PartialValue::new_iname("testperson1")
2507 )),
2508 EntryClass::Account.into(),
2510 "class name uuid",
2512 );
2513
2514 let acp2 = AccessControlCreate::from_raw(
2515 "test_create_2",
2516 Uuid::new_v4(),
2517 UUID_TEST_GROUP_1,
2519 filter_valid!(f_eq(
2521 Attribute::Name,
2522 PartialValue::new_iname("testperson1")
2523 )),
2524 EntryClass::Group.into(),
2526 "class name uuid",
2528 );
2529
2530 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
2532 test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
2534 test_acp_create!(&ce_admin, vec![acp.clone()], &r3_set, false);
2536 test_acp_create!(&ce_admin, vec![acp, acp2], &r4_set, false);
2538 }
2539
2540 #[test]
2541 fn test_access_enforce_scope_create() {
2542 let ev1 = entry_init!(
2543 (Attribute::Class, EntryClass::Account.to_value()),
2544 (Attribute::Name, Value::new_iname("testperson1")),
2545 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2546 );
2547 let r1_set = vec![ev1];
2548
2549 let admin = E_TEST_ACCOUNT_1.clone();
2550
2551 let ce_admin_ro = CreateEvent::new_impersonate_identity(
2552 Identity::from_impersonate_entry_readonly(admin.clone()),
2553 vec![],
2554 );
2555
2556 let ce_admin_rw = CreateEvent::new_impersonate_identity(
2557 Identity::from_impersonate_entry_readwrite(admin),
2558 vec![],
2559 );
2560
2561 let acp = AccessControlCreate::from_raw(
2562 "test_create",
2563 Uuid::new_v4(),
2564 UUID_TEST_GROUP_1,
2566 filter_valid!(f_eq(
2569 Attribute::Name,
2570 PartialValue::new_iname("testperson1")
2571 )),
2572 EntryClass::Account.into(),
2574 "class name uuid",
2576 );
2577
2578 test_acp_create!(&ce_admin_ro, vec![acp.clone()], &r1_set, false);
2579
2580 test_acp_create!(&ce_admin_rw, vec![acp], &r1_set, true);
2581 }
2582
2583 macro_rules! test_acp_delete {
2584 (
2585 $de:expr,
2586 $controls:expr,
2587 $entries:expr,
2588 $expect:expr
2589 ) => {{
2590 let ac = AccessControls::default();
2591 let mut acw = ac.write();
2592 acw.update_delete($controls).expect("Failed to update");
2593 let acw = acw;
2594
2595 let res = acw
2596 .delete_allow_operation($de, $entries)
2597 .expect("op failed");
2598
2599 debug!("result --> {:?}", res);
2600 debug!("expect --> {:?}", $expect);
2601 assert_eq!(res, $expect);
2603 }};
2604 }
2605
2606 #[test]
2607 fn test_access_enforce_delete() {
2608 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2609 let r_set = vec![Arc::new(ev1)];
2610
2611 let de_admin = DeleteEvent::new_impersonate_entry(
2612 E_TEST_ACCOUNT_1.clone(),
2613 filter_all!(f_eq(
2614 Attribute::Name,
2615 PartialValue::new_iname("testperson1")
2616 )),
2617 );
2618
2619 let de_anon = DeleteEvent::new_impersonate_entry(
2620 E_TEST_ACCOUNT_2.clone(),
2621 filter_all!(f_eq(
2622 Attribute::Name,
2623 PartialValue::new_iname("testperson1")
2624 )),
2625 );
2626
2627 let acp = AccessControlDelete::from_raw(
2628 "test_delete",
2629 Uuid::new_v4(),
2630 UUID_TEST_GROUP_1,
2632 filter_valid!(f_eq(
2634 Attribute::Name,
2635 PartialValue::new_iname("testperson1")
2636 )),
2637 );
2638
2639 test_acp_delete!(&de_admin, vec![acp.clone()], &r_set, true);
2641 test_acp_delete!(&de_anon, vec![acp], &r_set, false);
2643 }
2644
2645 #[test]
2646 fn test_access_enforce_scope_delete() {
2647 sketching::test_init();
2648 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2649 let r_set = vec![Arc::new(ev1)];
2650
2651 let admin = E_TEST_ACCOUNT_1.clone();
2652
2653 let de_admin_ro = DeleteEvent::new_impersonate_identity(
2654 Identity::from_impersonate_entry_readonly(admin.clone()),
2655 filter_all!(f_eq(
2656 Attribute::Name,
2657 PartialValue::new_iname("testperson1")
2658 )),
2659 );
2660
2661 let de_admin_rw = DeleteEvent::new_impersonate_identity(
2662 Identity::from_impersonate_entry_readwrite(admin),
2663 filter_all!(f_eq(
2664 Attribute::Name,
2665 PartialValue::new_iname("testperson1")
2666 )),
2667 );
2668
2669 let acp = AccessControlDelete::from_raw(
2670 "test_delete",
2671 Uuid::new_v4(),
2672 UUID_TEST_GROUP_1,
2674 filter_valid!(f_eq(
2676 Attribute::Name,
2677 PartialValue::new_iname("testperson1")
2678 )),
2679 );
2680
2681 test_acp_delete!(&de_admin_ro, vec![acp.clone()], &r_set, false);
2682
2683 test_acp_delete!(&de_admin_rw, vec![acp], &r_set, true);
2684 }
2685
2686 macro_rules! test_acp_effective_permissions {
2687 (
2688 $ident:expr,
2689 $attrs:expr,
2690 $search_controls:expr,
2691 $modify_controls:expr,
2692 $entries:expr,
2693 $expect:expr
2694 ) => {{
2695 let ac = AccessControls::default();
2696 let mut acw = ac.write();
2697 acw.update_search($search_controls)
2698 .expect("Failed to update");
2699 acw.update_modify($modify_controls)
2700 .expect("Failed to update");
2701 let acw = acw;
2702
2703 let res = acw
2704 .effective_permission_check($ident, $attrs, $entries)
2705 .expect("Failed to apply effective_permission_check");
2706
2707 debug!("result --> {:?}", res);
2708 debug!("expect --> {:?}", $expect);
2709 assert_eq!(res, $expect);
2711 }};
2712 }
2713
2714 #[test]
2715 fn test_access_effective_permission_check_1() {
2716 sketching::test_init();
2717
2718 let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
2719
2720 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2721
2722 let r_set = vec![Arc::new(ev1)];
2723
2724 test_acp_effective_permissions!(
2725 &admin,
2726 None,
2727 vec![AccessControlSearch::from_raw(
2728 "test_acp",
2729 Uuid::new_v4(),
2730 UUID_TEST_GROUP_1,
2732 filter_valid!(f_eq(
2734 Attribute::Name,
2735 PartialValue::new_iname("testperson1")
2736 )),
2737 Attribute::Name.as_ref(),
2739 )],
2740 vec![],
2741 &r_set,
2742 vec![AccessEffectivePermission {
2743 ident: UUID_TEST_ACCOUNT_1,
2744 delete: false,
2745 target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2746 search: Access::Allow(btreeset![Attribute::Name]),
2747 modify_pres: Access::Allow(BTreeSet::new()),
2748 modify_rem: Access::Allow(BTreeSet::new()),
2749 modify_pres_class: AccessClass::Allow(BTreeSet::new()),
2750 modify_rem_class: AccessClass::Allow(BTreeSet::new()),
2751 }]
2752 )
2753 }
2754
2755 #[test]
2756 fn test_access_effective_permission_check_2() {
2757 sketching::test_init();
2758
2759 let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
2760
2761 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2762
2763 let r_set = vec![Arc::new(ev1)];
2764
2765 test_acp_effective_permissions!(
2766 &admin,
2767 None,
2768 vec![],
2769 vec![AccessControlModify::from_raw(
2770 "test_acp",
2771 Uuid::new_v4(),
2772 UUID_TEST_GROUP_1,
2774 filter_valid!(f_eq(
2776 Attribute::Name,
2777 PartialValue::new_iname("testperson1")
2778 )),
2779 Attribute::Name.as_ref(),
2781 Attribute::Name.as_ref(),
2782 EntryClass::Object.into(),
2783 EntryClass::Object.into(),
2784 )],
2785 &r_set,
2786 vec![AccessEffectivePermission {
2787 ident: UUID_TEST_ACCOUNT_1,
2788 delete: false,
2789 target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2790 search: Access::Allow(BTreeSet::new()),
2791 modify_pres: Access::Allow(btreeset![Attribute::Name]),
2792 modify_rem: Access::Allow(btreeset![Attribute::Name]),
2793 modify_pres_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
2794 modify_rem_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
2795 }]
2796 )
2797 }
2798
2799 #[test]
2800 fn test_access_sync_authority_create() {
2801 sketching::test_init();
2802
2803 let ce_admin = CreateEvent::new_impersonate_identity(
2804 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2805 vec![],
2806 );
2807
2808 let ev1 = entry_init!(
2810 (Attribute::Class, EntryClass::Account.to_value()),
2811 (Attribute::Name, Value::new_iname("testperson1")),
2812 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2813 );
2814 let r1_set = vec![ev1];
2815
2816 let ev2 = entry_init!(
2817 (Attribute::Class, EntryClass::Account.to_value()),
2818 (Attribute::Class, EntryClass::SyncObject.to_value()),
2819 (Attribute::Name, Value::new_iname("testperson1")),
2820 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2821 );
2822 let r2_set = vec![ev2];
2823
2824 let acp = AccessControlCreate::from_raw(
2825 "test_create",
2826 Uuid::new_v4(),
2827 UUID_TEST_GROUP_1,
2829 filter_valid!(f_eq(
2832 Attribute::Name,
2833 PartialValue::new_iname("testperson1")
2834 )),
2835 "account sync_object",
2837 "class name uuid",
2839 );
2840
2841 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
2843 test_acp_create!(&ce_admin, vec![acp], &r2_set, false);
2845 }
2846
2847 #[test]
2848 fn test_access_sync_authority_delete() {
2849 sketching::test_init();
2850
2851 let ev1 = entry_init!(
2852 (Attribute::Class, EntryClass::Account.to_value()),
2853 (Attribute::Name, Value::new_iname("testperson1")),
2854 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2855 )
2856 .into_sealed_committed();
2857 let r1_set = vec![Arc::new(ev1)];
2858
2859 let ev2 = entry_init!(
2860 (Attribute::Class, EntryClass::Account.to_value()),
2861 (Attribute::Class, EntryClass::SyncObject.to_value()),
2862 (Attribute::Name, Value::new_iname("testperson1")),
2863 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2864 )
2865 .into_sealed_committed();
2866 let r2_set = vec![Arc::new(ev2)];
2867
2868 let de_admin = DeleteEvent::new_impersonate_entry(
2869 E_TEST_ACCOUNT_1.clone(),
2870 filter_all!(f_eq(
2871 Attribute::Name,
2872 PartialValue::new_iname("testperson1")
2873 )),
2874 );
2875
2876 let acp = AccessControlDelete::from_raw(
2877 "test_delete",
2878 Uuid::new_v4(),
2879 UUID_TEST_GROUP_1,
2881 filter_valid!(f_eq(
2883 Attribute::Name,
2884 PartialValue::new_iname("testperson1")
2885 )),
2886 );
2887
2888 test_acp_delete!(&de_admin, vec![acp.clone()], &r1_set, true);
2890 test_acp_delete!(&de_admin, vec![acp], &r2_set, false);
2892 }
2893
2894 #[test]
2895 fn test_access_sync_authority_modify() {
2896 sketching::test_init();
2897
2898 let ev1 = entry_init!(
2899 (Attribute::Class, EntryClass::Account.to_value()),
2900 (Attribute::Name, Value::new_iname("testperson1")),
2901 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2902 )
2903 .into_sealed_committed();
2904 let r1_set = vec![Arc::new(ev1)];
2905
2906 let sync_uuid = Uuid::new_v4();
2907 let ev2 = entry_init!(
2908 (Attribute::Class, EntryClass::Account.to_value()),
2909 (Attribute::Class, EntryClass::SyncObject.to_value()),
2910 (Attribute::SyncParentUuid, Value::Refer(sync_uuid)),
2911 (Attribute::Name, Value::new_iname("testperson1")),
2912 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2913 )
2914 .into_sealed_committed();
2915 let r2_set = vec![Arc::new(ev2)];
2916
2917 let acp_allow = AccessControlModify::from_raw(
2919 "test_modify_allow",
2920 Uuid::new_v4(),
2921 UUID_TEST_GROUP_1,
2923 filter_valid!(f_eq(
2925 Attribute::Name,
2926 PartialValue::new_iname("testperson1")
2927 )),
2928 &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
2930 &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
2932 EntryClass::Account.into(),
2934 EntryClass::Account.into(),
2935 );
2936
2937 let me_pres = ModifyEvent::new_impersonate_entry(
2941 E_TEST_ACCOUNT_1.clone(),
2942 filter_all!(f_eq(
2943 Attribute::Name,
2944 PartialValue::new_iname("testperson1")
2945 )),
2946 modlist!([m_pres(
2947 Attribute::UserAuthTokenSession,
2948 &Value::new_iname("value")
2949 )]),
2950 );
2951 let me_rem = ModifyEvent::new_impersonate_entry(
2953 E_TEST_ACCOUNT_1.clone(),
2954 filter_all!(f_eq(
2955 Attribute::Name,
2956 PartialValue::new_iname("testperson1")
2957 )),
2958 modlist!([m_remove(
2959 Attribute::UserAuthTokenSession,
2960 &PartialValue::new_iname("value")
2961 )]),
2962 );
2963 let me_purge = ModifyEvent::new_impersonate_entry(
2965 E_TEST_ACCOUNT_1.clone(),
2966 filter_all!(f_eq(
2967 Attribute::Name,
2968 PartialValue::new_iname("testperson1")
2969 )),
2970 modlist!([m_purge(Attribute::UserAuthTokenSession)]),
2971 );
2972
2973 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
2975 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r1_set, true);
2977 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r1_set, true);
2979
2980 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, true);
2982 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, true);
2984 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, true);
2986
2987 let me_pres = ModifyEvent::new_impersonate_entry(
2989 E_TEST_ACCOUNT_1.clone(),
2990 filter_all!(f_eq(
2991 Attribute::Name,
2992 PartialValue::new_iname("testperson1")
2993 )),
2994 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2995 );
2996 let me_rem = ModifyEvent::new_impersonate_entry(
2998 E_TEST_ACCOUNT_1.clone(),
2999 filter_all!(f_eq(
3000 Attribute::Name,
3001 PartialValue::new_iname("testperson1")
3002 )),
3003 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
3004 );
3005 let me_purge = ModifyEvent::new_impersonate_entry(
3007 E_TEST_ACCOUNT_1.clone(),
3008 filter_all!(f_eq(
3009 Attribute::Name,
3010 PartialValue::new_iname("testperson1")
3011 )),
3012 modlist!([m_purge(Attribute::Name)]),
3013 );
3014
3015 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
3017 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, false);
3019 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, false);
3021
3022 test_acp_modify!(
3027 &me_pres,
3028 vec![acp_allow.clone()],
3029 sync_uuid,
3030 Attribute::Name,
3031 &r2_set,
3032 true
3033 );
3034 test_acp_modify!(
3036 &me_rem,
3037 vec![acp_allow.clone()],
3038 sync_uuid,
3039 Attribute::Name,
3040 &r2_set,
3041 true
3042 );
3043 test_acp_modify!(
3045 &me_purge,
3046 vec![acp_allow],
3047 sync_uuid,
3048 Attribute::Name,
3049 &r2_set,
3050 true
3051 );
3052 }
3053
3054 #[test]
3055 fn test_access_oauth2_dyn_search() {
3056 sketching::test_init();
3057 let rs_uuid = Uuid::new_v4();
3060 let ev1 = entry_init!(
3061 (Attribute::Class, EntryClass::Object.to_value()),
3062 (
3063 Attribute::Class,
3064 EntryClass::OAuth2ResourceServer.to_value()
3065 ),
3066 (
3067 Attribute::Class,
3068 EntryClass::OAuth2ResourceServerBasic.to_value()
3069 ),
3070 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3071 (Attribute::Name, Value::new_iname("test_resource_server")),
3072 (
3073 Attribute::DisplayName,
3074 Value::new_utf8s("test_resource_server")
3075 ),
3076 (
3077 Attribute::OAuth2RsOriginLanding,
3078 Value::new_url_s("https://demo.example.com").unwrap()
3079 ),
3080 (
3081 Attribute::OAuth2RsOrigin,
3082 Value::new_url_s("app://hidden").unwrap()
3083 ),
3084 (
3085 Attribute::OAuth2RsScopeMap,
3086 Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["groups".to_string()])
3087 .expect("invalid oauthscope")
3088 ),
3089 (
3090 Attribute::OAuth2RsSupScopeMap,
3091 Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["supplement".to_string()])
3092 .expect("invalid oauthscope")
3093 ),
3094 (
3095 Attribute::OAuth2AllowInsecureClientDisablePkce,
3096 Value::new_bool(true)
3097 ),
3098 (
3099 Attribute::OAuth2JwtLegacyCryptoEnable,
3100 Value::new_bool(false)
3101 ),
3102 (Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
3103 )
3104 .into_sealed_committed();
3105
3106 let ev1_reduced = entry_init!(
3107 (Attribute::Class, EntryClass::Object.to_value()),
3108 (
3109 Attribute::Class,
3110 EntryClass::OAuth2ResourceServer.to_value()
3111 ),
3112 (
3113 Attribute::Class,
3114 EntryClass::OAuth2ResourceServerBasic.to_value()
3115 ),
3116 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3117 (Attribute::Name, Value::new_iname("test_resource_server")),
3118 (
3119 Attribute::DisplayName,
3120 Value::new_utf8s("test_resource_server")
3121 ),
3122 (
3123 Attribute::OAuth2RsOriginLanding,
3124 Value::new_url_s("https://demo.example.com").unwrap()
3125 )
3126 )
3127 .into_sealed_committed();
3128
3129 let ev2 = entry_init!(
3130 (Attribute::Class, EntryClass::Object.to_value()),
3131 (
3132 Attribute::Class,
3133 EntryClass::OAuth2ResourceServer.to_value()
3134 ),
3135 (
3136 Attribute::Class,
3137 EntryClass::OAuth2ResourceServerBasic.to_value()
3138 ),
3139 (Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
3140 (Attribute::Name, Value::new_iname("second_resource_server")),
3141 (
3142 Attribute::DisplayName,
3143 Value::new_utf8s("second_resource_server")
3144 ),
3145 (
3146 Attribute::OAuth2RsOriginLanding,
3147 Value::new_url_s("https://noaccess.example.com").unwrap()
3148 ),
3149 (
3150 Attribute::OAuth2RsOrigin,
3151 Value::new_url_s("app://hidden").unwrap()
3152 ),
3153 (
3154 Attribute::OAuth2RsScopeMap,
3155 Value::new_oauthscopemap(UUID_SYSTEM_ADMINS, btreeset!["groups".to_string()])
3156 .expect("invalid oauthscope")
3157 ),
3158 (
3159 Attribute::OAuth2RsSupScopeMap,
3160 Value::new_oauthscopemap(
3161 UUID_TEST_GROUP_1,
3163 btreeset!["supplement".to_string()]
3164 )
3165 .expect("invalid oauthscope")
3166 ),
3167 (
3168 Attribute::OAuth2AllowInsecureClientDisablePkce,
3169 Value::new_bool(true)
3170 ),
3171 (
3172 Attribute::OAuth2JwtLegacyCryptoEnable,
3173 Value::new_bool(false)
3174 ),
3175 (Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
3176 )
3177 .into_sealed_committed();
3178
3179 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
3180
3181 let se_a = SearchEvent::new_impersonate_entry(
3183 E_TEST_ACCOUNT_1.clone(),
3184 filter_all!(f_pres(Attribute::Name)),
3185 );
3186 let ex_a = vec![Arc::new(ev1)];
3187 let ex_a_reduced = vec![ev1_reduced];
3188
3189 test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
3190 test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
3191
3192 let anon: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
3194 let mut anon = anon.into_invalid_new();
3195 anon.set_ava_set(&Attribute::MemberOf, ValueSetRefer::new(UUID_TEST_GROUP_1));
3196
3197 let anon = Arc::new(anon.into_sealed_committed());
3198
3199 let se_anon =
3200 SearchEvent::new_impersonate_entry(anon, filter_all!(f_pres(Attribute::Name)));
3201 let ex_anon = vec![];
3202 test_acp_search!(&se_anon, vec![], r_set.clone(), ex_anon);
3203
3204 let se_b = SearchEvent::new_impersonate_entry(
3206 E_TEST_ACCOUNT_2.clone(),
3207 filter_all!(f_pres(Attribute::Name)),
3208 );
3209 let ex_b = vec![];
3210
3211 test_acp_search!(&se_b, vec![], r_set, ex_b);
3212 }
3213
3214 #[test]
3215 fn test_access_sync_account_dyn_search() {
3216 sketching::test_init();
3217 let sync_uuid = Uuid::new_v4();
3222 let portal_url = Url::parse("https://localhost/portal").unwrap();
3223
3224 let ev1 = entry_init!(
3225 (Attribute::Class, EntryClass::Object.to_value()),
3226 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3227 (Attribute::Uuid, Value::Uuid(sync_uuid)),
3228 (Attribute::Name, Value::new_iname("test_sync_account")),
3229 (
3230 Attribute::SyncCredentialPortal,
3231 Value::Url(portal_url.clone())
3232 )
3233 )
3234 .into_sealed_committed();
3235
3236 let ev1_reduced = entry_init!(
3237 (Attribute::Class, EntryClass::Object.to_value()),
3238 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3239 (Attribute::Uuid, Value::Uuid(sync_uuid)),
3240 (
3241 Attribute::SyncCredentialPortal,
3242 Value::Url(portal_url.clone())
3243 )
3244 )
3245 .into_sealed_committed();
3246
3247 let ev2 = entry_init!(
3248 (Attribute::Class, EntryClass::Object.to_value()),
3249 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3250 (Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
3251 (Attribute::Name, Value::new_iname("test_sync_account")),
3252 (
3253 Attribute::SyncCredentialPortal,
3254 Value::Url(portal_url.clone())
3255 )
3256 )
3257 .into_sealed_committed();
3258
3259 let sync_test_account: Arc<EntrySealedCommitted> = Arc::new(
3260 entry_init!(
3261 (Attribute::Class, EntryClass::Object.to_value()),
3262 (Attribute::Class, EntryClass::Account.to_value()),
3263 (Attribute::Class, EntryClass::SyncObject.to_value()),
3264 (Attribute::Name, Value::new_iname("test_account_1")),
3265 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3266 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3267 (Attribute::SyncParentUuid, Value::Refer(sync_uuid))
3268 )
3269 .into_sealed_committed(),
3270 );
3271
3272 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
3274
3275 let se_a = SearchEvent::new_impersonate_entry(
3276 sync_test_account,
3277 filter_all!(f_pres(Attribute::SyncCredentialPortal)),
3278 );
3279 let ex_a = vec![Arc::new(ev1)];
3280 let ex_a_reduced = vec![ev1_reduced];
3281
3282 test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
3283 test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
3284
3285 let se_b = SearchEvent::new_impersonate_entry(
3287 E_TEST_ACCOUNT_2.clone(),
3288 filter_all!(f_pres(Attribute::SyncCredentialPortal)),
3289 );
3290 let ex_b = vec![];
3291
3292 test_acp_search!(&se_b, vec![], r_set, ex_b);
3293 }
3294
3295 #[test]
3296 fn test_access_entry_managed_by_search() {
3297 sketching::test_init();
3298
3299 let test_entry = Arc::new(
3300 entry_init!(
3301 (Attribute::Class, EntryClass::Object.to_value()),
3302 (Attribute::Name, Value::new_iname("testperson1")),
3303 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3304 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3305 )
3306 .into_sealed_committed(),
3307 );
3308
3309 let data_set = vec![test_entry.clone()];
3310
3311 let se_a = SearchEvent::new_impersonate_entry(
3312 E_TEST_ACCOUNT_1.clone(),
3313 filter_all!(f_pres(Attribute::Name)),
3314 );
3315 let expect_a = vec![test_entry];
3316
3317 let se_b = SearchEvent::new_impersonate_entry(
3318 E_TEST_ACCOUNT_2.clone(),
3319 filter_all!(f_pres(Attribute::Name)),
3320 );
3321 let expect_b = vec![];
3322
3323 let acp = AccessControlSearch::from_managed_by(
3324 "test_acp",
3325 Uuid::new_v4(),
3326 AccessControlTarget::Scope(filter_valid!(f_eq(
3328 Attribute::Name,
3329 PartialValue::new_iname("testperson1")
3330 ))),
3331 Attribute::Name.as_ref(),
3334 );
3335
3336 test_acp_search!(&se_a, vec![acp.clone()], data_set.clone(), expect_a);
3338
3339 test_acp_search!(&se_b, vec![acp], data_set, expect_b);
3341 }
3342
3343 #[test]
3344 fn test_access_entry_managed_by_create() {
3345 sketching::test_init();
3346
3347 let test_entry = entry_init!(
3348 (Attribute::Class, EntryClass::Object.to_value()),
3349 (Attribute::Name, Value::new_iname("testperson1")),
3350 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3351 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3352 );
3353
3354 let data_set = vec![test_entry];
3355
3356 let ce = CreateEvent::new_impersonate_identity(
3357 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
3358 vec![],
3359 );
3360
3361 let acp = AccessControlCreate::from_managed_by(
3362 "test_create",
3363 Uuid::new_v4(),
3364 AccessControlTarget::Scope(filter_valid!(f_eq(
3365 Attribute::Name,
3366 PartialValue::new_iname("testperson1")
3367 ))),
3368 EntryClass::Account.into(),
3370 "class name uuid",
3372 );
3373
3374 test_acp_create!(&ce, vec![acp.clone()], &data_set, false);
3377 }
3378
3379 #[test]
3380 fn test_access_entry_managed_by_modify() {
3381 let test_entry = Arc::new(
3382 entry_init!(
3383 (Attribute::Class, EntryClass::Object.to_value()),
3384 (Attribute::Name, Value::new_iname("testperson1")),
3385 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3386 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3387 )
3388 .into_sealed_committed(),
3389 );
3390
3391 let data_set = vec![test_entry];
3392
3393 let me_pres = ModifyEvent::new_impersonate_entry(
3395 E_TEST_ACCOUNT_1.clone(),
3396 filter_all!(f_eq(
3397 Attribute::Name,
3398 PartialValue::new_iname("testperson1")
3399 )),
3400 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
3401 );
3402 let me_rem = ModifyEvent::new_impersonate_entry(
3404 E_TEST_ACCOUNT_1.clone(),
3405 filter_all!(f_eq(
3406 Attribute::Name,
3407 PartialValue::new_iname("testperson1")
3408 )),
3409 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
3410 );
3411 let me_purge = ModifyEvent::new_impersonate_entry(
3413 E_TEST_ACCOUNT_1.clone(),
3414 filter_all!(f_eq(
3415 Attribute::Name,
3416 PartialValue::new_iname("testperson1")
3417 )),
3418 modlist!([m_purge(Attribute::Name)]),
3419 );
3420
3421 let acp_allow = AccessControlModify::from_managed_by(
3422 "test_modify_allow",
3423 Uuid::new_v4(),
3424 AccessControlTarget::Scope(filter_valid!(f_eq(
3426 Attribute::Name,
3427 PartialValue::new_iname("testperson1")
3428 ))),
3429 "name class",
3431 "name class",
3433 EntryClass::Account.into(),
3435 EntryClass::Account.into(),
3436 );
3437
3438 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &data_set, true);
3440 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &data_set, true);
3442 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &data_set, true);
3444 }
3445
3446 #[test]
3447 fn test_access_entry_managed_by_delete() {
3448 let test_entry = Arc::new(
3449 entry_init!(
3450 (Attribute::Class, EntryClass::Object.to_value()),
3451 (Attribute::Name, Value::new_iname("testperson1")),
3452 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3453 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3454 )
3455 .into_sealed_committed(),
3456 );
3457
3458 let data_set = vec![test_entry];
3459
3460 let de_a = DeleteEvent::new_impersonate_entry(
3461 E_TEST_ACCOUNT_1.clone(),
3462 filter_all!(f_eq(
3463 Attribute::Name,
3464 PartialValue::new_iname("testperson1")
3465 )),
3466 );
3467
3468 let de_b = DeleteEvent::new_impersonate_entry(
3469 E_TEST_ACCOUNT_2.clone(),
3470 filter_all!(f_eq(
3471 Attribute::Name,
3472 PartialValue::new_iname("testperson1")
3473 )),
3474 );
3475
3476 let acp = AccessControlDelete::from_managed_by(
3477 "test_delete",
3478 Uuid::new_v4(),
3479 AccessControlTarget::Scope(filter_valid!(f_eq(
3481 Attribute::Name,
3482 PartialValue::new_iname("testperson1")
3483 ))),
3484 );
3485
3486 test_acp_delete!(&de_a, vec![acp.clone()], &data_set, true);
3488 test_acp_delete!(&de_b, vec![acp], &data_set, false);
3490 }
3491
3492 #[test]
3493 fn test_access_delete_protect_system_ranges() {
3494 let ev1: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
3495 let ev1 = ev1.into_sealed_committed();
3496 let r_set = vec![Arc::new(ev1)];
3497
3498 let de_account = DeleteEvent::new_impersonate_entry(
3499 E_TEST_ACCOUNT_1.clone(),
3500 filter_all!(f_eq(
3501 Attribute::Name,
3502 PartialValue::new_iname("testperson1")
3503 )),
3504 );
3505
3506 let acp = AccessControlDelete::from_raw(
3507 "test_delete",
3508 Uuid::new_v4(),
3509 UUID_TEST_GROUP_1,
3510 filter_valid!(f_eq(Attribute::Name, PartialValue::new_iname("anonymous"))),
3512 );
3513
3514 test_acp_delete!(&de_account, vec![acp], &r_set, false);
3516 }
3517
3518 #[test]
3519 fn test_access_sync_memberof_implies_directmemberof() {
3520 sketching::test_init();
3521
3522 let ev1 = entry_init!(
3523 (Attribute::Class, EntryClass::Object.to_value()),
3524 (Attribute::Name, Value::new_iname("test_account_1")),
3525 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3526 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3527 (Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
3528 )
3529 .into_sealed_committed();
3530 let r_set = vec![Arc::new(ev1)];
3531
3532 let exv1 = entry_init!(
3533 (Attribute::Name, Value::new_iname("test_account_1")),
3534 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3535 (Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
3536 )
3537 .into_sealed_committed();
3538
3539 let ex_anon_some = vec![exv1];
3540
3541 let se_anon_ro = SearchEvent::new_impersonate_identity(
3542 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
3543 filter_all!(f_pres(Attribute::Name)),
3544 );
3545
3546 let acp = AccessControlSearch::from_raw(
3547 "test_acp",
3548 Uuid::new_v4(),
3549 UUID_TEST_GROUP_1,
3551 filter_valid!(f_eq(
3553 Attribute::Uuid,
3554 PartialValue::Uuid(UUID_TEST_ACCOUNT_1)
3555 )),
3556 format!("{} {}", Attribute::Name, Attribute::MemberOf).as_str(),
3559 );
3560
3561 test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
3563 }
3564
3565 #[test]
3566 fn test_access_protected_deny_create() {
3567 sketching::test_init();
3568
3569 let ev1 = entry_init!(
3570 (Attribute::Class, EntryClass::Account.to_value()),
3571 (Attribute::Name, Value::new_iname("testperson1")),
3572 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3573 );
3574 let r1_set = vec![ev1];
3575
3576 let ev2 = entry_init!(
3577 (Attribute::Class, EntryClass::Account.to_value()),
3578 (Attribute::Class, EntryClass::System.to_value()),
3579 (Attribute::Name, Value::new_iname("testperson1")),
3580 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3581 );
3582
3583 let r2_set = vec![ev2];
3584
3585 let ce_admin = CreateEvent::new_impersonate_identity(
3586 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
3587 vec![],
3588 );
3589
3590 let acp = AccessControlCreate::from_raw(
3591 "test_create",
3592 Uuid::new_v4(),
3593 UUID_TEST_GROUP_1,
3595 filter_valid!(f_eq(
3598 Attribute::Name,
3599 PartialValue::new_iname("testperson1")
3600 )),
3601 EntryClass::Account.into(),
3603 "class name uuid",
3605 );
3606
3607 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
3609 test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
3611 }
3612
3613 #[test]
3614 fn test_access_protected_deny_delete() {
3615 sketching::test_init();
3616
3617 let ev1 = entry_init!(
3618 (Attribute::Class, EntryClass::Account.to_value()),
3619 (Attribute::Name, Value::new_iname("testperson1")),
3620 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3621 )
3622 .into_sealed_committed();
3623 let r1_set = vec![Arc::new(ev1)];
3624
3625 let ev2 = entry_init!(
3626 (Attribute::Class, EntryClass::Account.to_value()),
3627 (Attribute::Class, EntryClass::System.to_value()),
3628 (Attribute::Name, Value::new_iname("testperson1")),
3629 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3630 )
3631 .into_sealed_committed();
3632
3633 let r2_set = vec![Arc::new(ev2)];
3634
3635 let de = DeleteEvent::new_impersonate_entry(
3636 E_TEST_ACCOUNT_1.clone(),
3637 filter_all!(f_eq(
3638 Attribute::Name,
3639 PartialValue::new_iname("testperson1")
3640 )),
3641 );
3642
3643 let acp = AccessControlDelete::from_raw(
3644 "test_delete",
3645 Uuid::new_v4(),
3646 UUID_TEST_GROUP_1,
3648 filter_valid!(f_eq(
3650 Attribute::Name,
3651 PartialValue::new_iname("testperson1")
3652 )),
3653 );
3654
3655 test_acp_delete!(&de, vec![acp.clone()], &r1_set, true);
3657 test_acp_delete!(&de, vec![acp.clone()], &r2_set, false);
3659 }
3660
3661 #[test]
3662 fn test_access_protected_deny_modify() {
3663 sketching::test_init();
3664
3665 let ev1 = entry_init!(
3666 (Attribute::Class, EntryClass::Account.to_value()),
3667 (Attribute::Name, Value::new_iname("testperson1")),
3668 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3669 )
3670 .into_sealed_committed();
3671 let r1_set = vec![Arc::new(ev1)];
3672
3673 let ev2 = entry_init!(
3674 (Attribute::Class, EntryClass::Account.to_value()),
3675 (Attribute::Class, EntryClass::System.to_value()),
3676 (Attribute::Name, Value::new_iname("testperson1")),
3677 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3678 )
3679 .into_sealed_committed();
3680
3681 let r2_set = vec![Arc::new(ev2)];
3682
3683 let acp_allow = AccessControlModify::from_raw(
3685 "test_modify_allow",
3686 Uuid::new_v4(),
3687 UUID_TEST_GROUP_1,
3689 filter_valid!(f_eq(
3691 Attribute::Name,
3692 PartialValue::new_iname("testperson1")
3693 )),
3694 "displayname class",
3696 "displayname class",
3698 "system recycled",
3700 "system recycled",
3701 );
3702
3703 let me_pres = ModifyEvent::new_impersonate_entry(
3704 E_TEST_ACCOUNT_1.clone(),
3705 filter_all!(f_eq(
3706 Attribute::Name,
3707 PartialValue::new_iname("testperson1")
3708 )),
3709 modlist!([m_pres(Attribute::DisplayName, &Value::new_utf8s("value"))]),
3710 );
3711
3712 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
3714
3715 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
3717
3718 let me_rem_sys = ModifyEvent::new_impersonate_entry(
3720 E_TEST_ACCOUNT_1.clone(),
3721 filter_all!(f_eq(
3722 Attribute::Class,
3723 PartialValue::new_iname("testperson1")
3724 )),
3725 modlist!([m_remove(
3726 Attribute::Class,
3727 &EntryClass::System.to_partialvalue()
3728 )]),
3729 );
3730
3731 test_acp_modify!(&me_rem_sys, vec![acp_allow.clone()], &r2_set, false);
3732
3733 let me_pres = ModifyEvent::new_impersonate_entry(
3735 E_TEST_ACCOUNT_1.clone(),
3736 filter_all!(f_eq(
3737 Attribute::Name,
3738 PartialValue::new_iname("testperson1")
3739 )),
3740 modlist!([m_pres(Attribute::Class, &EntryClass::Recycled.to_value())]),
3741 );
3742
3743 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, false);
3744 }
3745}