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