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<'b>(
374 &'b 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 requested_pres_classes.extend(v.as_iutf8_iter().into_iter().flatten());
750 requested_rem_classes.extend(v.as_iutf8_iter().into_iter().flatten());
751 }
752 }
753 _ => {}
754 }
755 }
756
757 debug!(?requested_pres, "Requested present set");
758 debug!(?requested_rem, "Requested remove set");
759 debug!(?requested_pres_classes, "Requested present class set");
760 debug!(?requested_rem_classes, "Requested remove class set");
761 debug!(entry_id = %e.get_display_id());
762
763 let sync_agmts = self.get_sync_agreements();
764
765 match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
766 ModifyResult::Deny => false,
767 ModifyResult::Grant => true,
768 ModifyResult::Allow {
769 pres,
770 rem,
771 pres_cls,
772 rem_cls,
773 } => {
774 let mut decision = true;
775
776 if !requested_pres.is_subset(&pres) {
777 security_error!("requested_pres is not a subset of allowed");
778 security_error!(
779 "requested_pres: {:?} !⊆ allowed: {:?}",
780 requested_pres,
781 pres
782 );
783 decision = false
784 };
785
786 if !requested_rem.is_subset(&rem) {
787 security_error!("requested_rem is not a subset of allowed");
788 security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
789 decision = false;
790 };
791
792 if !requested_pres_classes.is_subset(&pres_cls) {
793 security_error!("requested_pres_classes is not a subset of allowed");
794 security_error!(
795 "requested_classes: {:?} !⊆ allowed: {:?}",
796 requested_pres_classes,
797 pres_cls
798 );
799 decision = false;
800 };
801
802 if !requested_rem_classes.is_subset(&rem_cls) {
803 security_error!("requested_rem_classes is not a subset of allowed");
804 security_error!(
805 "requested_classes: {:?} !⊆ allowed: {:?}",
806 requested_rem_classes,
807 rem_cls
808 );
809 decision = false;
810 }
811
812 if decision {
813 debug!("passed pres, rem, classes check.");
814 }
815
816 decision
818 }
819 }
820 });
821
822 if r {
823 debug!("allowed modify of {} entries ✅", entries.len());
824 } else {
825 security_access!("denied ❌ - modifications may not proceed");
826 }
827 Ok(r)
828 }
829
830 #[instrument(level = "debug", name = "access::create_allow_operation", skip_all)]
831 fn create_allow_operation(
832 &self,
833 ce: &CreateEvent,
834 entries: &[Entry<EntryInit, EntryNew>],
835 ) -> Result<bool, OperationError> {
836 let create_state = self.get_create();
838 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
839
840 let ident_memberof = ce.ident.get_memberof();
841
842 let related_acp: Vec<_> = create_state
844 .iter()
845 .filter_map(|acs| {
846 let (receiver_condition, target_condition) = resolve_access_conditions(
847 &ce.ident,
848 ident_memberof,
849 &acs.acp.receiver,
850 &acs.acp.target,
851 acp_resolve_filter_cache,
852 )?;
853
854 Some(AccessControlCreateResolved {
855 acp: acs,
856 receiver_condition,
857 target_condition,
858 })
859 })
860 .collect();
861
862 let r = entries.iter().all(|e| {
864 match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
865 CreateResult::Deny => false,
866 CreateResult::Grant => true,
867 }
868 });
869
870 if r {
871 debug!("allowed create of {} entries ✅", entries.len());
872 } else {
873 security_access!("denied ❌ - create may not proceed");
874 }
875
876 Ok(r)
877 }
878
879 #[instrument(level = "trace", name = "access::delete_related_acp", skip_all)]
880 fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
881 let delete_state = self.get_delete();
883 let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
884
885 let ident_memberof = ident.get_memberof();
886
887 let related_acp: Vec<_> = delete_state
888 .iter()
889 .filter_map(|acs| {
890 let (receiver_condition, target_condition) = resolve_access_conditions(
891 ident,
892 ident_memberof,
893 &acs.acp.receiver,
894 &acs.acp.target,
895 acp_resolve_filter_cache,
896 )?;
897
898 Some(AccessControlDeleteResolved {
899 acp: acs,
900 receiver_condition,
901 target_condition,
902 })
903 })
904 .collect();
905
906 related_acp
907 }
908
909 #[instrument(level = "debug", name = "access::delete_allow_operation", skip_all)]
910 fn delete_allow_operation(
911 &self,
912 de: &DeleteEvent,
913 entries: &[Arc<EntrySealedCommitted>],
914 ) -> Result<bool, OperationError> {
915 let related_acp = self.delete_related_acp(&de.ident);
917
918 let r = entries.iter().all(|e| {
920 match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
921 DeleteResult::Deny => false,
922 DeleteResult::Grant => true,
923 }
924 });
925 if r {
926 debug!("allowed delete of {} entries ✅", entries.len());
927 } else {
928 security_access!("denied ❌ - delete may not proceed");
929 }
930 Ok(r)
931 }
932
933 #[instrument(level = "debug", name = "access::effective_permission_check", skip_all)]
934 fn effective_permission_check(
935 &self,
936 ident: &Identity,
937 attrs: Option<BTreeSet<Attribute>>,
938 entries: &[Arc<EntrySealedCommitted>],
939 ) -> Result<Vec<AccessEffectivePermission>, OperationError> {
940 let ident_uuid = match &ident.origin {
949 IdentType::Internal => {
950 security_critical!("IMPOSSIBLE STATE: Internal search in external interface?! Returning empty for safety.");
953 return Err(OperationError::InvalidState);
955 }
956 IdentType::Synch(_) => {
957 security_critical!("Blocking sync check");
958 return Err(OperationError::InvalidState);
959 }
960 IdentType::User(u) => u.entry.get_uuid(),
961 };
962
963 trace!(ident = %ident, "Effective permission check");
964 let search_related_acp = self.search_related_acp(ident, attrs.as_ref());
969 let modify_related_acp = self.modify_related_acp(ident);
971 let delete_related_acp = self.delete_related_acp(ident);
973
974 let sync_agmts = self.get_sync_agreements();
975
976 let effective_permissions: Vec<_> = entries
977 .iter()
978 .map(|entry| {
979 self.entry_effective_permission_check(
980 ident,
981 ident_uuid,
982 entry,
983 &search_related_acp,
984 &modify_related_acp,
985 &delete_related_acp,
986 sync_agmts,
987 )
988 })
989 .collect();
990
991 effective_permissions.iter().for_each(|ep| {
992 trace!(?ep);
993 });
994
995 Ok(effective_permissions)
996 }
997
998 fn entry_effective_permission_check<'b>(
999 &'b self,
1000 ident: &Identity,
1001 ident_uuid: Uuid,
1002 entry: &Arc<EntrySealedCommitted>,
1003 search_related_acp: &[AccessControlSearchResolved<'b>],
1004 modify_related_acp: &[AccessControlModifyResolved<'b>],
1005 delete_related_acp: &[AccessControlDeleteResolved<'b>],
1006 sync_agmts: &HashMap<Uuid, BTreeSet<Attribute>>,
1007 ) -> AccessEffectivePermission {
1008 let search_effective = match apply_search_access(ident, search_related_acp, entry) {
1010 SearchResult::Deny => Access::Deny,
1011 SearchResult::Grant => Access::Grant,
1012 SearchResult::Allow(allowed_attrs) => {
1013 Access::Allow(allowed_attrs.into_iter().collect())
1015 }
1016 };
1017
1018 let (modify_pres, modify_rem, modify_pres_class, modify_rem_class) =
1020 match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
1021 ModifyResult::Deny => (
1022 Access::Deny,
1023 Access::Deny,
1024 AccessClass::Deny,
1025 AccessClass::Deny,
1026 ),
1027 ModifyResult::Grant => (
1028 Access::Grant,
1029 Access::Grant,
1030 AccessClass::Grant,
1031 AccessClass::Grant,
1032 ),
1033 ModifyResult::Allow {
1034 pres,
1035 rem,
1036 pres_cls,
1037 rem_cls,
1038 } => (
1039 Access::Allow(pres.into_iter().collect()),
1040 Access::Allow(rem.into_iter().collect()),
1041 AccessClass::Allow(pres_cls.into_iter().map(|s| s.into()).collect()),
1042 AccessClass::Allow(rem_cls.into_iter().map(|s| s.into()).collect()),
1043 ),
1044 };
1045
1046 let delete_status = apply_delete_access(ident, delete_related_acp, entry);
1048
1049 let delete = match delete_status {
1050 DeleteResult::Deny => false,
1051 DeleteResult::Grant => true,
1052 };
1053
1054 AccessEffectivePermission {
1055 ident: ident_uuid,
1056 target: entry.get_uuid(),
1057 delete,
1058 search: search_effective,
1059 modify_pres,
1060 modify_rem,
1061 modify_pres_class,
1062 modify_rem_class,
1063 }
1064 }
1065}
1066
1067pub struct AccessControlsWriteTransaction<'a> {
1068 inner: CowCellWriteTxn<'a, AccessControlsInner>,
1069 acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
1070}
1071
1072impl AccessControlsWriteTransaction<'_> {
1073 pub fn update_search(
1077 &mut self,
1078 mut acps: Vec<AccessControlSearch>,
1079 ) -> Result<(), OperationError> {
1080 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_search);
1081 Ok(())
1082 }
1083
1084 pub fn update_create(
1085 &mut self,
1086 mut acps: Vec<AccessControlCreate>,
1087 ) -> Result<(), OperationError> {
1088 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_create);
1089 Ok(())
1090 }
1091
1092 pub fn update_modify(
1093 &mut self,
1094 mut acps: Vec<AccessControlModify>,
1095 ) -> Result<(), OperationError> {
1096 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_modify);
1097 Ok(())
1098 }
1099
1100 pub fn update_delete(
1101 &mut self,
1102 mut acps: Vec<AccessControlDelete>,
1103 ) -> Result<(), OperationError> {
1104 std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_delete);
1105 Ok(())
1106 }
1107
1108 pub fn update_sync_agreements(
1109 &mut self,
1110 mut sync_agreements: HashMap<Uuid, BTreeSet<Attribute>>,
1111 ) {
1112 std::mem::swap(
1113 &mut sync_agreements,
1114 &mut self.inner.deref_mut().sync_agreements,
1115 );
1116 }
1117
1118 pub fn commit(self) -> Result<(), OperationError> {
1119 self.inner.commit();
1120
1121 Ok(())
1122 }
1123}
1124
1125impl<'a> AccessControlsTransaction<'a> for AccessControlsWriteTransaction<'a> {
1126 fn get_search(&self) -> &Vec<AccessControlSearch> {
1127 &self.inner.acps_search
1128 }
1129
1130 fn get_create(&self) -> &Vec<AccessControlCreate> {
1131 &self.inner.acps_create
1132 }
1133
1134 fn get_modify(&self) -> &Vec<AccessControlModify> {
1135 &self.inner.acps_modify
1136 }
1137
1138 fn get_delete(&self) -> &Vec<AccessControlDelete> {
1139 &self.inner.acps_delete
1140 }
1141
1142 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
1143 &self.inner.sync_agreements
1144 }
1145
1146 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
1147 unsafe {
1148 let mptr = self.acp_resolve_filter_cache.as_ptr();
1149 &mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
1150 }
1151 }
1152}
1153
1154pub struct AccessControlsReadTransaction<'a> {
1159 inner: CowCellReadTxn<AccessControlsInner>,
1160 acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
1162}
1163
1164unsafe impl Sync for AccessControlsReadTransaction<'_> {}
1165
1166unsafe impl Send for AccessControlsReadTransaction<'_> {}
1167
1168impl<'a> AccessControlsTransaction<'a> for AccessControlsReadTransaction<'a> {
1169 fn get_search(&self) -> &Vec<AccessControlSearch> {
1170 &self.inner.acps_search
1171 }
1172
1173 fn get_create(&self) -> &Vec<AccessControlCreate> {
1174 &self.inner.acps_create
1175 }
1176
1177 fn get_modify(&self) -> &Vec<AccessControlModify> {
1178 &self.inner.acps_modify
1179 }
1180
1181 fn get_delete(&self) -> &Vec<AccessControlDelete> {
1182 &self.inner.acps_delete
1183 }
1184
1185 fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
1186 &self.inner.sync_agreements
1187 }
1188
1189 fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
1190 unsafe {
1191 let mptr = self.acp_resolve_filter_cache.as_ptr();
1192 &mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
1193 }
1194 }
1195}
1196
1197impl Default for AccessControls {
1202 #![allow(clippy::expect_used)]
1203 fn default() -> Self {
1204 AccessControls {
1205 inner: CowCell::new(AccessControlsInner {
1206 acps_search: Vec::with_capacity(0),
1207 acps_create: Vec::with_capacity(0),
1208 acps_modify: Vec::with_capacity(0),
1209 acps_delete: Vec::with_capacity(0),
1210 sync_agreements: HashMap::default(),
1211 }),
1212 acp_resolve_filter_cache: ARCacheBuilder::new()
1215 .set_size(ACP_RESOLVE_FILTER_CACHE_MAX, ACP_RESOLVE_FILTER_CACHE_LOCAL)
1216 .set_reader_quiesce(true)
1217 .build()
1218 .expect("Failed to construct acp_resolve_filter_cache"),
1219 }
1220 }
1221}
1222
1223impl AccessControls {
1224 pub fn try_quiesce(&self) {
1225 self.acp_resolve_filter_cache.try_quiesce();
1226 }
1227
1228 pub fn read(&self) -> AccessControlsReadTransaction {
1229 AccessControlsReadTransaction {
1230 inner: self.inner.read(),
1231 acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
1233 }
1234 }
1235
1236 pub fn write(&self) -> AccessControlsWriteTransaction {
1237 AccessControlsWriteTransaction {
1238 inner: self.inner.write(),
1239 acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
1242 }
1243 }
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248 use hashbrown::HashMap;
1249 use std::collections::BTreeSet;
1250 use std::sync::Arc;
1251
1252 use uuid::uuid;
1253
1254 use super::{
1255 profiles::{
1256 AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlProfile,
1257 AccessControlSearch, AccessControlTarget,
1258 },
1259 Access, AccessClass, AccessControls, AccessControlsTransaction, AccessEffectivePermission,
1260 };
1261 use crate::migration_data::BUILTIN_ACCOUNT_ANONYMOUS;
1262 use crate::prelude::*;
1263 use crate::valueset::ValueSetIname;
1264
1265 const UUID_TEST_ACCOUNT_1: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
1266 const UUID_TEST_ACCOUNT_2: Uuid = uuid::uuid!("cec0852a-abdf-4ea6-9dae-d3157cb33d3a");
1267 const UUID_TEST_GROUP_1: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
1268 const UUID_TEST_GROUP_2: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
1269
1270 lazy_static! {
1271 pub static ref E_TEST_ACCOUNT_1: Arc<EntrySealedCommitted> = Arc::new(
1272 entry_init!(
1273 (Attribute::Class, EntryClass::Object.to_value()),
1274 (Attribute::Name, Value::new_iname("test_account_1")),
1275 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
1276 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1))
1277 )
1278 .into_sealed_committed()
1279 );
1280 pub static ref E_TEST_ACCOUNT_2: Arc<EntrySealedCommitted> = Arc::new(
1281 entry_init!(
1282 (Attribute::Class, EntryClass::Object.to_value()),
1283 (Attribute::Name, Value::new_iname("test_account_1")),
1284 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_2)),
1285 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_2))
1286 )
1287 .into_sealed_committed()
1288 );
1289 }
1290
1291 macro_rules! acp_from_entry_err {
1292 (
1293 $qs:expr,
1294 $e:expr,
1295 $type:ty
1296 ) => {{
1297 let ev1 = $e.into_sealed_committed();
1298
1299 let r1 = <$type>::try_from($qs, &ev1);
1300 error!(?r1);
1301 assert!(r1.is_err());
1302 }};
1303 }
1304
1305 macro_rules! acp_from_entry_ok {
1306 (
1307 $qs:expr,
1308 $e:expr,
1309 $type:ty
1310 ) => {{
1311 let ev1 = $e.into_sealed_committed();
1312
1313 let r1 = <$type>::try_from($qs, &ev1);
1314 assert!(r1.is_ok());
1315 r1.unwrap()
1316 }};
1317 }
1318
1319 #[qs_test]
1320 async fn test_access_acp_parser(qs: &QueryServer) {
1321 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1330
1331 acp_from_entry_err!(
1332 &mut qs_write,
1333 entry_init!(
1334 (Attribute::Class, EntryClass::Object.to_value()),
1335 (Attribute::Name, Value::new_iname("acp_invalid")),
1336 (
1337 Attribute::Uuid,
1338 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1339 )
1340 ),
1341 AccessControlProfile
1342 );
1343
1344 acp_from_entry_err!(
1345 &mut qs_write,
1346 entry_init!(
1347 (Attribute::Class, EntryClass::Object.to_value()),
1348 (
1349 Attribute::Class,
1350 EntryClass::AccessControlProfile.to_value()
1351 ),
1352 (
1353 Attribute::Class,
1354 EntryClass::AccessControlReceiverGroup.to_value()
1355 ),
1356 (
1357 Attribute::Class,
1358 EntryClass::AccessControlTargetScope.to_value()
1359 ),
1360 (Attribute::Name, Value::new_iname("acp_invalid")),
1361 (
1362 Attribute::Uuid,
1363 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1364 )
1365 ),
1366 AccessControlProfile
1367 );
1368
1369 acp_from_entry_err!(
1370 &mut qs_write,
1371 entry_init!(
1372 (Attribute::Class, EntryClass::Object.to_value()),
1373 (
1374 Attribute::Class,
1375 EntryClass::AccessControlProfile.to_value()
1376 ),
1377 (
1378 Attribute::Class,
1379 EntryClass::AccessControlReceiverGroup.to_value()
1380 ),
1381 (
1382 Attribute::Class,
1383 EntryClass::AccessControlTargetScope.to_value()
1384 ),
1385 (Attribute::Name, Value::new_iname("acp_invalid")),
1386 (
1387 Attribute::Uuid,
1388 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1389 ),
1390 (Attribute::AcpReceiverGroup, Value::Bool(true)),
1391 (Attribute::AcpTargetScope, Value::Bool(true))
1392 ),
1393 AccessControlProfile
1394 );
1395
1396 acp_from_entry_ok!(
1398 &mut qs_write,
1399 entry_init!(
1400 (Attribute::Class, EntryClass::Object.to_value()),
1401 (
1402 Attribute::Class,
1403 EntryClass::AccessControlProfile.to_value()
1404 ),
1405 (
1406 Attribute::Class,
1407 EntryClass::AccessControlReceiverGroup.to_value()
1408 ),
1409 (
1410 Attribute::Class,
1411 EntryClass::AccessControlTargetScope.to_value()
1412 ),
1413 (Attribute::Name, Value::new_iname("acp_valid")),
1414 (
1415 Attribute::Uuid,
1416 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1417 ),
1418 (
1419 Attribute::AcpReceiverGroup,
1420 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1421 ),
1422 (
1423 Attribute::AcpTargetScope,
1424 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1425 )
1426 ),
1427 AccessControlProfile
1428 );
1429 }
1430
1431 #[qs_test]
1432 async fn test_access_acp_delete_parser(qs: &QueryServer) {
1433 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1434
1435 acp_from_entry_err!(
1436 &mut qs_write,
1437 entry_init!(
1438 (Attribute::Class, EntryClass::Object.to_value()),
1439 (
1440 Attribute::Class,
1441 EntryClass::AccessControlProfile.to_value()
1442 ),
1443 (Attribute::Name, Value::new_iname("acp_valid")),
1444 (
1445 Attribute::Uuid,
1446 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1447 ),
1448 (
1449 Attribute::AcpReceiverGroup,
1450 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1451 ),
1452 (
1453 Attribute::AcpTargetScope,
1454 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1455 )
1456 ),
1457 AccessControlDelete
1458 );
1459
1460 acp_from_entry_ok!(
1461 &mut qs_write,
1462 entry_init!(
1463 (Attribute::Class, EntryClass::Object.to_value()),
1464 (
1465 Attribute::Class,
1466 EntryClass::AccessControlProfile.to_value()
1467 ),
1468 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
1469 (Attribute::Name, Value::new_iname("acp_valid")),
1470 (
1471 Attribute::Uuid,
1472 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1473 ),
1474 (
1475 Attribute::AcpReceiverGroup,
1476 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1477 ),
1478 (
1479 Attribute::AcpTargetScope,
1480 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1481 )
1482 ),
1483 AccessControlDelete
1484 );
1485 }
1486
1487 #[qs_test]
1488 async fn test_access_acp_search_parser(qs: &QueryServer) {
1489 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1491
1492 acp_from_entry_err!(
1494 &mut qs_write,
1495 entry_init!(
1496 (Attribute::Class, EntryClass::Object.to_value()),
1497 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1498 (Attribute::Name, Value::new_iname("acp_valid")),
1499 (
1500 Attribute::Uuid,
1501 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1502 ),
1503 (
1504 Attribute::AcpReceiverGroup,
1505 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1506 ),
1507 (
1508 Attribute::AcpTargetScope,
1509 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1510 ),
1511 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1512 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1513 ),
1514 AccessControlSearch
1515 );
1516
1517 acp_from_entry_err!(
1519 &mut qs_write,
1520 entry_init!(
1521 (Attribute::Class, EntryClass::Object.to_value()),
1522 (
1523 Attribute::Class,
1524 EntryClass::AccessControlProfile.to_value()
1525 ),
1526 (Attribute::Name, Value::new_iname("acp_valid")),
1527 (
1528 Attribute::Uuid,
1529 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1530 ),
1531 (
1532 Attribute::AcpReceiverGroup,
1533 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1534 ),
1535 (
1536 Attribute::AcpTargetScope,
1537 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1538 ),
1539 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1540 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1541 ),
1542 AccessControlSearch
1543 );
1544
1545 acp_from_entry_err!(
1547 &mut qs_write,
1548 entry_init!(
1549 (Attribute::Class, EntryClass::Object.to_value()),
1550 (
1551 Attribute::Class,
1552 EntryClass::AccessControlProfile.to_value()
1553 ),
1554 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1555 (Attribute::Name, Value::new_iname("acp_valid")),
1556 (
1557 Attribute::Uuid,
1558 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1559 ),
1560 (
1561 Attribute::AcpReceiverGroup,
1562 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1563 ),
1564 (
1565 Attribute::AcpTargetScope,
1566 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1567 )
1568 ),
1569 AccessControlSearch
1570 );
1571
1572 acp_from_entry_ok!(
1574 &mut qs_write,
1575 entry_init!(
1576 (Attribute::Class, EntryClass::Object.to_value()),
1577 (
1578 Attribute::Class,
1579 EntryClass::AccessControlProfile.to_value()
1580 ),
1581 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1582 (Attribute::Name, Value::new_iname("acp_valid")),
1583 (
1584 Attribute::Uuid,
1585 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1586 ),
1587 (
1588 Attribute::AcpReceiverGroup,
1589 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1590 ),
1591 (
1592 Attribute::AcpTargetScope,
1593 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1594 ),
1595 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1596 (Attribute::AcpSearchAttr, Value::new_iutf8("class"))
1597 ),
1598 AccessControlSearch
1599 );
1600 }
1601
1602 #[qs_test]
1603 async fn test_access_acp_modify_parser(qs: &QueryServer) {
1604 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1606
1607 acp_from_entry_err!(
1608 &mut qs_write,
1609 entry_init!(
1610 (Attribute::Class, EntryClass::Object.to_value()),
1611 (
1612 Attribute::Class,
1613 EntryClass::AccessControlProfile.to_value()
1614 ),
1615 (Attribute::Name, Value::new_iname("acp_invalid")),
1616 (
1617 Attribute::Uuid,
1618 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1619 ),
1620 (
1621 Attribute::AcpReceiverGroup,
1622 Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1623 ),
1624 (
1625 Attribute::AcpTargetScope,
1626 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1627 )
1628 ),
1629 AccessControlModify
1630 );
1631
1632 acp_from_entry_ok!(
1633 &mut qs_write,
1634 entry_init!(
1635 (Attribute::Class, EntryClass::Object.to_value()),
1636 (
1637 Attribute::Class,
1638 EntryClass::AccessControlProfile.to_value()
1639 ),
1640 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1641 (Attribute::Name, Value::new_iname("acp_valid")),
1642 (
1643 Attribute::Uuid,
1644 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1645 ),
1646 (
1647 Attribute::AcpReceiverGroup,
1648 Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1649 ),
1650 (
1651 Attribute::AcpTargetScope,
1652 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1653 )
1654 ),
1655 AccessControlModify
1656 );
1657
1658 acp_from_entry_ok!(
1659 &mut qs_write,
1660 entry_init!(
1661 (Attribute::Class, EntryClass::Object.to_value()),
1662 (
1663 Attribute::Class,
1664 EntryClass::AccessControlProfile.to_value()
1665 ),
1666 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1667 (Attribute::Name, Value::new_iname("acp_valid")),
1668 (
1669 Attribute::Uuid,
1670 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1671 ),
1672 (
1673 Attribute::AcpReceiverGroup,
1674 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1675 ),
1676 (
1677 Attribute::AcpTargetScope,
1678 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1679 ),
1680 (
1681 Attribute::AcpModifyRemovedAttr,
1682 Value::from(Attribute::Name)
1683 ),
1684 (
1685 Attribute::AcpModifyPresentAttr,
1686 Value::from(Attribute::Name)
1687 ),
1688 (Attribute::AcpModifyClass, EntryClass::Object.to_value())
1689 ),
1690 AccessControlModify
1691 );
1692 }
1693
1694 #[qs_test]
1695 async fn test_access_acp_create_parser(qs: &QueryServer) {
1696 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1698
1699 acp_from_entry_err!(
1700 &mut qs_write,
1701 entry_init!(
1702 (Attribute::Class, EntryClass::Object.to_value()),
1703 (
1704 Attribute::Class,
1705 EntryClass::AccessControlProfile.to_value()
1706 ),
1707 (Attribute::Name, Value::new_iname("acp_invalid")),
1708 (
1709 Attribute::Uuid,
1710 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1711 ),
1712 (
1713 Attribute::AcpReceiverGroup,
1714 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1715 ),
1716 (
1717 Attribute::AcpTargetScope,
1718 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1719 ),
1720 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1721 (Attribute::AcpCreateClass, EntryClass::Object.to_value())
1722 ),
1723 AccessControlCreate
1724 );
1725
1726 acp_from_entry_ok!(
1727 &mut qs_write,
1728 entry_init!(
1729 (Attribute::Class, EntryClass::Object.to_value()),
1730 (
1731 Attribute::Class,
1732 EntryClass::AccessControlProfile.to_value()
1733 ),
1734 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1735 (Attribute::Name, Value::new_iname("acp_valid")),
1736 (
1737 Attribute::Uuid,
1738 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1739 ),
1740 (
1741 Attribute::AcpReceiverGroup,
1742 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1743 ),
1744 (
1745 Attribute::AcpTargetScope,
1746 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1747 )
1748 ),
1749 AccessControlCreate
1750 );
1751
1752 acp_from_entry_ok!(
1753 &mut qs_write,
1754 entry_init!(
1755 (Attribute::Class, EntryClass::Object.to_value()),
1756 (
1757 Attribute::Class,
1758 EntryClass::AccessControlProfile.to_value()
1759 ),
1760 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1761 (Attribute::Name, Value::new_iname("acp_valid")),
1762 (
1763 Attribute::Uuid,
1764 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1765 ),
1766 (
1767 Attribute::AcpReceiverGroup,
1768 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1769 ),
1770 (
1771 Attribute::AcpTargetScope,
1772 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1773 ),
1774 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1775 (Attribute::AcpCreateClass, EntryClass::Object.to_value())
1776 ),
1777 AccessControlCreate
1778 );
1779 }
1780
1781 #[qs_test]
1782 async fn test_access_acp_compound_parser(qs: &QueryServer) {
1783 let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
1788
1789 let e = entry_init!(
1790 (Attribute::Class, EntryClass::Object.to_value()),
1791 (
1792 Attribute::Class,
1793 EntryClass::AccessControlProfile.to_value()
1794 ),
1795 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
1796 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
1797 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
1798 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
1799 (Attribute::Name, Value::new_iname("acp_valid")),
1800 (
1801 Attribute::Uuid,
1802 Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1803 ),
1804 (
1805 Attribute::AcpReceiverGroup,
1806 Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1807 ),
1808 (
1809 Attribute::AcpTargetScope,
1810 Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
1811 ),
1812 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
1813 (Attribute::AcpCreateClass, EntryClass::Class.to_value()),
1814 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
1815 (
1816 Attribute::AcpModifyRemovedAttr,
1817 Value::from(Attribute::Name)
1818 ),
1819 (
1820 Attribute::AcpModifyPresentAttr,
1821 Value::from(Attribute::Name)
1822 ),
1823 (Attribute::AcpModifyClass, EntryClass::Object.to_value())
1824 );
1825
1826 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlCreate);
1827 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlDelete);
1828 acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlModify);
1829 acp_from_entry_ok!(&mut qs_write, e, AccessControlSearch);
1830 }
1831
1832 macro_rules! test_acp_search {
1833 (
1834 $se:expr,
1835 $controls:expr,
1836 $entries:expr,
1837 $expect:expr
1838 ) => {{
1839 let ac = AccessControls::default();
1840 let mut acw = ac.write();
1841 acw.update_search($controls).expect("Failed to update");
1842 let acw = acw;
1843
1844 let res = acw
1845 .search_filter_entries(&mut $se, $entries)
1846 .expect("op failed");
1847 debug!("result --> {:?}", res);
1848 debug!("expect --> {:?}", $expect);
1849 assert_eq!(res, $expect);
1851 }};
1852 }
1853
1854 macro_rules! test_acp_search_reduce {
1855 (
1856 $se:expr,
1857 $controls:expr,
1858 $entries:expr,
1859 $expect:expr
1860 ) => {{
1861 let ac = AccessControls::default();
1862 let mut acw = ac.write();
1863 acw.update_search($controls).expect("Failed to update");
1864 let acw = acw;
1865
1866 let res = acw
1868 .search_filter_entries(&mut $se, $entries)
1869 .expect("operation failed");
1870 let reduced = acw
1872 .search_filter_entry_attributes(&mut $se, res)
1873 .expect("operation failed");
1874
1875 let expect_set: Vec<Entry<EntryReduced, EntryCommitted>> =
1877 $expect.into_iter().map(|e| e.into_reduced()).collect();
1878
1879 debug!("expect --> {:?}", expect_set);
1880 debug!("result --> {:?}", reduced);
1881 assert_eq!(reduced, expect_set);
1883 }};
1884 }
1885
1886 #[test]
1887 fn test_access_internal_search() {
1888 let se = SearchEvent::new_internal_invalid(filter!(f_pres(Attribute::Class)));
1890
1891 let expect = vec![E_TEST_ACCOUNT_1.clone()];
1892 let entries = vec![E_TEST_ACCOUNT_1.clone()];
1893
1894 test_acp_search!(
1896 &se,
1897 vec![AccessControlSearch::from_raw(
1898 "test_acp",
1899 Uuid::new_v4(),
1900 UUID_TEST_GROUP_1,
1901 filter_valid!(f_pres(Attribute::NonExist)), Attribute::Name.as_ref(), )],
1904 entries,
1905 expect
1906 );
1907 }
1908
1909 #[test]
1910 fn test_access_enforce_search() {
1911 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
1913 let ev2 = E_TESTPERSON_2.clone().into_sealed_committed();
1914
1915 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
1916
1917 let se_a = SearchEvent::new_impersonate_entry(
1918 E_TEST_ACCOUNT_1.clone(),
1919 filter_all!(f_pres(Attribute::Name)),
1920 );
1921 let ex_a = vec![Arc::new(ev1)];
1922
1923 let se_b = SearchEvent::new_impersonate_entry(
1924 E_TEST_ACCOUNT_2.clone(),
1925 filter_all!(f_pres(Attribute::Name)),
1926 );
1927 let ex_b = vec![];
1928
1929 let acp = AccessControlSearch::from_raw(
1930 "test_acp",
1931 Uuid::new_v4(),
1932 UUID_TEST_GROUP_1,
1934 filter_valid!(f_eq(
1936 Attribute::Name,
1937 PartialValue::new_iname("testperson1")
1938 )),
1939 Attribute::Name.as_ref(),
1942 );
1943
1944 test_acp_search!(&se_a, vec![acp.clone()], r_set.clone(), ex_a);
1946
1947 test_acp_search!(&se_b, vec![acp], r_set, ex_b);
1949 }
1950
1951 #[test]
1952 fn test_access_enforce_scope_search() {
1953 sketching::test_init();
1954 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
1956
1957 let ex_some = vec![Arc::new(ev1.clone())];
1958
1959 let r_set = vec![Arc::new(ev1)];
1960
1961 let se_ro = SearchEvent::new_impersonate_identity(
1962 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
1963 filter_all!(f_pres(Attribute::Name)),
1964 );
1965
1966 let se_rw = SearchEvent::new_impersonate_identity(
1967 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
1968 filter_all!(f_pres(Attribute::Name)),
1969 );
1970
1971 let acp = AccessControlSearch::from_raw(
1972 "test_acp",
1973 Uuid::new_v4(),
1974 UUID_TEST_GROUP_1,
1976 filter_valid!(f_eq(
1978 Attribute::Name,
1979 PartialValue::new_iname("testperson1")
1980 )),
1981 Attribute::Name.as_ref(),
1984 );
1985
1986 test_acp_search!(&se_ro, vec![acp.clone()], r_set.clone(), ex_some);
1988
1989 test_acp_search!(&se_rw, vec![acp], r_set, ex_some);
1990 }
1991
1992 #[test]
1993 fn test_access_enforce_scope_search_attrs() {
1994 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
1998 let r_set = vec![Arc::new(ev1)];
1999
2000 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2001
2002 let ex_anon_some = vec![exv1];
2003
2004 let se_anon_ro = SearchEvent::new_impersonate_identity(
2005 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2006 filter_all!(f_pres(Attribute::Name)),
2007 );
2008
2009 let acp = AccessControlSearch::from_raw(
2010 "test_acp",
2011 Uuid::new_v4(),
2012 UUID_TEST_GROUP_1,
2014 filter_valid!(f_eq(
2016 Attribute::Name,
2017 PartialValue::new_iname("testperson1")
2018 )),
2019 Attribute::Name.as_ref(),
2022 );
2023
2024 test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
2026 }
2027
2028 lazy_static! {
2029 pub static ref E_TESTPERSON_1_REDUCED: EntryInitNew =
2030 entry_init!((Attribute::Name, Value::new_iname("testperson1")));
2031 }
2032
2033 #[test]
2034 fn test_access_enforce_search_attrs() {
2035 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2039 let r_set = vec![Arc::new(ev1)];
2040
2041 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2042 let ex_anon = vec![exv1];
2043
2044 let se_anon = SearchEvent::new_impersonate_entry(
2045 E_TEST_ACCOUNT_1.clone(),
2046 filter_all!(f_eq(
2047 Attribute::Name,
2048 PartialValue::new_iname("testperson1")
2049 )),
2050 );
2051
2052 let acp = AccessControlSearch::from_raw(
2053 "test_acp",
2054 Uuid::new_v4(),
2055 UUID_TEST_GROUP_1,
2057 filter_valid!(f_eq(
2059 Attribute::Name,
2060 PartialValue::new_iname("testperson1")
2061 )),
2062 Attribute::Name.as_ref(),
2065 );
2066
2067 test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
2069 }
2070
2071 #[test]
2072 fn test_access_enforce_search_attrs_req() {
2073 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2077
2078 let r_set = vec![Arc::new(ev1)];
2079
2080 let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
2081 let ex_anon = vec![exv1];
2082
2083 let mut se_anon = SearchEvent::new_impersonate_entry(
2084 E_TEST_ACCOUNT_1.clone(),
2085 filter_all!(f_eq(
2086 Attribute::Name,
2087 PartialValue::new_iname("testperson1")
2088 )),
2089 );
2090 se_anon.attrs = Some(btreeset![Attribute::Name]);
2092
2093 let acp = AccessControlSearch::from_raw(
2094 "test_acp",
2095 Uuid::new_v4(),
2096 UUID_TEST_GROUP_1,
2098 filter_valid!(f_eq(
2100 Attribute::Name,
2101 PartialValue::new_iname("testperson1")
2102 )),
2103 "name uuid",
2106 );
2107
2108 test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
2110 }
2111
2112 macro_rules! test_acp_modify {
2113 (
2114 $me:expr,
2115 $controls:expr,
2116 $entries:expr,
2117 $expect:expr
2118 ) => {{
2119 let ac = AccessControls::default();
2120 let mut acw = ac.write();
2121 acw.update_modify($controls).expect("Failed to update");
2122 let acw = acw;
2123
2124 let res = acw
2125 .modify_allow_operation(&mut $me, $entries)
2126 .expect("op failed");
2127
2128 debug!("result --> {:?}", res);
2129 debug!("expect --> {:?}", $expect);
2130 assert_eq!($expect, res);
2132 }};
2133 (
2134 $me:expr,
2135 $controls:expr,
2136 $sync_uuid:expr,
2137 $sync_yield_attr:expr,
2138 $entries:expr,
2139 $expect:expr
2140 ) => {{
2141 let ac = AccessControls::default();
2142 let mut acw = ac.write();
2143 acw.update_modify($controls).expect("Failed to update");
2144 let mut sync_agmt = HashMap::new();
2145 let mut set = BTreeSet::new();
2146 set.insert($sync_yield_attr);
2147 sync_agmt.insert($sync_uuid, set);
2148 acw.update_sync_agreements(sync_agmt);
2149 let acw = acw;
2150
2151 let res = acw
2152 .modify_allow_operation(&mut $me, $entries)
2153 .expect("op failed");
2154
2155 debug!("result --> {:?}", res);
2156 debug!("expect --> {:?}", $expect);
2157 assert_eq!($expect, res);
2159 }};
2160 }
2161
2162 #[test]
2163 fn test_access_enforce_modify() {
2164 sketching::test_init();
2165
2166 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2167 let r_set = vec![Arc::new(ev1)];
2168
2169 let me_pres = ModifyEvent::new_impersonate_entry(
2171 E_TEST_ACCOUNT_1.clone(),
2172 filter_all!(f_eq(
2173 Attribute::Name,
2174 PartialValue::new_iname("testperson1")
2175 )),
2176 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2177 );
2178 let me_rem = ModifyEvent::new_impersonate_entry(
2180 E_TEST_ACCOUNT_1.clone(),
2181 filter_all!(f_eq(
2182 Attribute::Name,
2183 PartialValue::new_iname("testperson1")
2184 )),
2185 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
2186 );
2187 let me_purge = ModifyEvent::new_impersonate_entry(
2189 E_TEST_ACCOUNT_1.clone(),
2190 filter_all!(f_eq(
2191 Attribute::Name,
2192 PartialValue::new_iname("testperson1")
2193 )),
2194 modlist!([m_purge(Attribute::Name)]),
2195 );
2196
2197 let me_set = ModifyEvent::new_impersonate_entry(
2199 E_TEST_ACCOUNT_1.clone(),
2200 filter_all!(f_eq(
2201 Attribute::Name,
2202 PartialValue::new_iname("testperson1")
2203 )),
2204 modlist!([Modify::Set(Attribute::Name, ValueSetIname::new("value"))]),
2205 );
2206
2207 let me_pres_class = ModifyEvent::new_impersonate_entry(
2209 E_TEST_ACCOUNT_1.clone(),
2210 filter_all!(f_eq(
2211 Attribute::Name,
2212 PartialValue::new_iname("testperson1")
2213 )),
2214 modlist!([m_pres(Attribute::Class, &EntryClass::Account.to_value())]),
2215 );
2216 let me_rem_class = ModifyEvent::new_impersonate_entry(
2218 E_TEST_ACCOUNT_1.clone(),
2219 filter_all!(f_eq(
2220 Attribute::Name,
2221 PartialValue::new_iname("testperson1")
2222 )),
2223 modlist!([m_remove(
2224 Attribute::Class,
2225 &EntryClass::Account.to_partialvalue()
2226 )]),
2227 );
2228 let me_purge_class = ModifyEvent::new_impersonate_entry(
2230 E_TEST_ACCOUNT_1.clone(),
2231 filter_all!(f_eq(
2232 Attribute::Name,
2233 PartialValue::new_iname("testperson1")
2234 )),
2235 modlist!([m_purge(Attribute::Class)]),
2236 );
2237
2238 let me_set_class = ModifyEvent::new_impersonate_entry(
2240 E_TEST_ACCOUNT_1.clone(),
2241 filter_all!(f_eq(
2242 Attribute::Name,
2243 PartialValue::new_iname("testperson1")
2244 )),
2245 modlist!([Modify::Set(
2246 Attribute::Class,
2247 EntryClass::Account.to_valueset()
2248 )]),
2249 );
2250
2251 let acp_allow = AccessControlModify::from_raw(
2253 "test_modify_allow",
2254 Uuid::new_v4(),
2255 UUID_TEST_GROUP_1,
2257 filter_valid!(f_eq(
2259 Attribute::Name,
2260 PartialValue::new_iname("testperson1")
2261 )),
2262 "name class",
2264 "name class",
2266 EntryClass::Account.into(),
2268 EntryClass::Account.into(),
2270 );
2271 let acp_deny = AccessControlModify::from_raw(
2273 "test_modify_deny",
2274 Uuid::new_v4(),
2275 UUID_TEST_GROUP_1,
2277 filter_valid!(f_eq(
2279 Attribute::Name,
2280 PartialValue::new_iname("testperson1")
2281 )),
2282 "member class",
2284 "member class",
2286 EntryClass::Group.into(),
2287 EntryClass::Group.into(),
2288 );
2289 let acp_no_class = AccessControlModify::from_raw(
2291 "test_modify_no_class",
2292 Uuid::new_v4(),
2293 UUID_TEST_GROUP_1,
2295 filter_valid!(f_eq(
2297 Attribute::Name,
2298 PartialValue::new_iname("testperson1")
2299 )),
2300 "name class",
2302 "name class",
2304 EntryClass::Group.into(),
2306 EntryClass::Group.into(),
2307 );
2308
2309 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r_set, true);
2311 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r_set, true);
2313 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r_set, true);
2315 test_acp_modify!(&me_set, vec![acp_allow.clone()], &r_set, true);
2317
2318 test_acp_modify!(&me_pres, vec![acp_deny.clone()], &r_set, false);
2320 test_acp_modify!(&me_rem, vec![acp_deny.clone()], &r_set, false);
2322 test_acp_modify!(&me_purge, vec![acp_deny.clone()], &r_set, false);
2324 test_acp_modify!(&me_set, vec![acp_deny.clone()], &r_set, false);
2326
2327 test_acp_modify!(&me_pres_class, vec![acp_allow.clone()], &r_set, true);
2329 test_acp_modify!(&me_rem_class, vec![acp_allow.clone()], &r_set, true);
2331 test_acp_modify!(&me_purge_class, vec![acp_allow.clone()], &r_set, false);
2333 test_acp_modify!(&me_set_class, vec![acp_allow], &r_set, true);
2335
2336 test_acp_modify!(&me_pres_class, vec![acp_no_class.clone()], &r_set, false);
2338 test_acp_modify!(&me_pres_class, vec![acp_deny.clone()], &r_set, false);
2340 test_acp_modify!(&me_rem_class, vec![acp_no_class.clone()], &r_set, false);
2342 test_acp_modify!(&me_rem_class, vec![acp_deny.clone()], &r_set, false);
2344
2345 test_acp_modify!(&me_set_class, vec![acp_no_class], &r_set, false);
2347 test_acp_modify!(&me_set_class, vec![acp_deny], &r_set, false);
2349 }
2350
2351 #[test]
2352 fn test_access_enforce_scope_modify() {
2353 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2354 let r_set = vec![Arc::new(ev1)];
2355
2356 let me_pres_ro = ModifyEvent::new_impersonate_identity(
2358 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
2359 filter_all!(f_eq(
2360 Attribute::Name,
2361 PartialValue::new_iname("testperson1")
2362 )),
2363 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2364 );
2365
2366 let me_pres_rw = ModifyEvent::new_impersonate_identity(
2368 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2369 filter_all!(f_eq(
2370 Attribute::Name,
2371 PartialValue::new_iname("testperson1")
2372 )),
2373 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2374 );
2375
2376 let acp_allow = AccessControlModify::from_raw(
2377 "test_modify_allow",
2378 Uuid::new_v4(),
2379 UUID_TEST_GROUP_1,
2381 filter_valid!(f_eq(
2383 Attribute::Name,
2384 PartialValue::new_iname("testperson1")
2385 )),
2386 "name class",
2388 "name class",
2390 EntryClass::Account.into(),
2392 EntryClass::Account.into(),
2393 );
2394
2395 test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
2396
2397 test_acp_modify!(&me_pres_rw, vec![acp_allow], &r_set, true);
2398 }
2399
2400 macro_rules! test_acp_create {
2401 (
2402 $ce:expr,
2403 $controls:expr,
2404 $entries:expr,
2405 $expect:expr
2406 ) => {{
2407 let ac = AccessControls::default();
2408 let mut acw = ac.write();
2409 acw.update_create($controls).expect("Failed to update");
2410 let acw = acw;
2411
2412 let res = acw
2413 .create_allow_operation(&mut $ce, $entries)
2414 .expect("op failed");
2415
2416 debug!("result --> {:?}", res);
2417 debug!("expect --> {:?}", $expect);
2418 assert_eq!(res, $expect);
2420 }};
2421 }
2422
2423 #[test]
2424 fn test_access_enforce_create() {
2425 let ev1 = entry_init!(
2426 (Attribute::Class, EntryClass::Account.to_value()),
2427 (Attribute::Name, Value::new_iname("testperson1")),
2428 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2429 );
2430 let r1_set = vec![ev1];
2431
2432 let ev2 = entry_init!(
2433 (Attribute::Class, EntryClass::Account.to_value()),
2434 (Attribute::TestNotAllowed, Value::new_class("notallowed")),
2435 (Attribute::Name, Value::new_iname("testperson1")),
2436 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2437 );
2438
2439 let r2_set = vec![ev2];
2440
2441 let ev3 = entry_init!(
2442 (Attribute::Class, EntryClass::Account.to_value()),
2443 (Attribute::Class, Value::new_class("notallowed")),
2444 (Attribute::Name, Value::new_iname("testperson1")),
2445 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2446 );
2447 let r3_set = vec![ev3];
2448
2449 let ev4 = entry_init!(
2450 (Attribute::Class, EntryClass::Account.to_value()),
2451 (Attribute::Class, EntryClass::Group.to_value()),
2452 (Attribute::Name, Value::new_iname("testperson1")),
2453 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2454 );
2455 let r4_set = vec![ev4];
2456
2457 let ce_admin = CreateEvent::new_impersonate_identity(
2464 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2465 vec![],
2466 );
2467
2468 let acp = AccessControlCreate::from_raw(
2469 "test_create",
2470 Uuid::new_v4(),
2471 UUID_TEST_GROUP_1,
2473 filter_valid!(f_eq(
2476 Attribute::Name,
2477 PartialValue::new_iname("testperson1")
2478 )),
2479 EntryClass::Account.into(),
2481 "class name uuid",
2483 );
2484
2485 let acp2 = AccessControlCreate::from_raw(
2486 "test_create_2",
2487 Uuid::new_v4(),
2488 UUID_TEST_GROUP_1,
2490 filter_valid!(f_eq(
2492 Attribute::Name,
2493 PartialValue::new_iname("testperson1")
2494 )),
2495 EntryClass::Group.into(),
2497 "class name uuid",
2499 );
2500
2501 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
2503 test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
2505 test_acp_create!(&ce_admin, vec![acp.clone()], &r3_set, false);
2507 test_acp_create!(&ce_admin, vec![acp, acp2], &r4_set, false);
2509 }
2510
2511 #[test]
2512 fn test_access_enforce_scope_create() {
2513 let ev1 = entry_init!(
2514 (Attribute::Class, EntryClass::Account.to_value()),
2515 (Attribute::Name, Value::new_iname("testperson1")),
2516 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2517 );
2518 let r1_set = vec![ev1];
2519
2520 let admin = E_TEST_ACCOUNT_1.clone();
2521
2522 let ce_admin_ro = CreateEvent::new_impersonate_identity(
2523 Identity::from_impersonate_entry_readonly(admin.clone()),
2524 vec![],
2525 );
2526
2527 let ce_admin_rw = CreateEvent::new_impersonate_identity(
2528 Identity::from_impersonate_entry_readwrite(admin),
2529 vec![],
2530 );
2531
2532 let acp = AccessControlCreate::from_raw(
2533 "test_create",
2534 Uuid::new_v4(),
2535 UUID_TEST_GROUP_1,
2537 filter_valid!(f_eq(
2540 Attribute::Name,
2541 PartialValue::new_iname("testperson1")
2542 )),
2543 EntryClass::Account.into(),
2545 "class name uuid",
2547 );
2548
2549 test_acp_create!(&ce_admin_ro, vec![acp.clone()], &r1_set, false);
2550
2551 test_acp_create!(&ce_admin_rw, vec![acp], &r1_set, true);
2552 }
2553
2554 macro_rules! test_acp_delete {
2555 (
2556 $de:expr,
2557 $controls:expr,
2558 $entries:expr,
2559 $expect:expr
2560 ) => {{
2561 let ac = AccessControls::default();
2562 let mut acw = ac.write();
2563 acw.update_delete($controls).expect("Failed to update");
2564 let acw = acw;
2565
2566 let res = acw
2567 .delete_allow_operation($de, $entries)
2568 .expect("op failed");
2569
2570 debug!("result --> {:?}", res);
2571 debug!("expect --> {:?}", $expect);
2572 assert_eq!(res, $expect);
2574 }};
2575 }
2576
2577 #[test]
2578 fn test_access_enforce_delete() {
2579 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2580 let r_set = vec![Arc::new(ev1)];
2581
2582 let de_admin = DeleteEvent::new_impersonate_entry(
2583 E_TEST_ACCOUNT_1.clone(),
2584 filter_all!(f_eq(
2585 Attribute::Name,
2586 PartialValue::new_iname("testperson1")
2587 )),
2588 );
2589
2590 let de_anon = DeleteEvent::new_impersonate_entry(
2591 E_TEST_ACCOUNT_2.clone(),
2592 filter_all!(f_eq(
2593 Attribute::Name,
2594 PartialValue::new_iname("testperson1")
2595 )),
2596 );
2597
2598 let acp = AccessControlDelete::from_raw(
2599 "test_delete",
2600 Uuid::new_v4(),
2601 UUID_TEST_GROUP_1,
2603 filter_valid!(f_eq(
2605 Attribute::Name,
2606 PartialValue::new_iname("testperson1")
2607 )),
2608 );
2609
2610 test_acp_delete!(&de_admin, vec![acp.clone()], &r_set, true);
2612 test_acp_delete!(&de_anon, vec![acp], &r_set, false);
2614 }
2615
2616 #[test]
2617 fn test_access_enforce_scope_delete() {
2618 sketching::test_init();
2619 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2620 let r_set = vec![Arc::new(ev1)];
2621
2622 let admin = E_TEST_ACCOUNT_1.clone();
2623
2624 let de_admin_ro = DeleteEvent::new_impersonate_identity(
2625 Identity::from_impersonate_entry_readonly(admin.clone()),
2626 filter_all!(f_eq(
2627 Attribute::Name,
2628 PartialValue::new_iname("testperson1")
2629 )),
2630 );
2631
2632 let de_admin_rw = DeleteEvent::new_impersonate_identity(
2633 Identity::from_impersonate_entry_readwrite(admin),
2634 filter_all!(f_eq(
2635 Attribute::Name,
2636 PartialValue::new_iname("testperson1")
2637 )),
2638 );
2639
2640 let acp = AccessControlDelete::from_raw(
2641 "test_delete",
2642 Uuid::new_v4(),
2643 UUID_TEST_GROUP_1,
2645 filter_valid!(f_eq(
2647 Attribute::Name,
2648 PartialValue::new_iname("testperson1")
2649 )),
2650 );
2651
2652 test_acp_delete!(&de_admin_ro, vec![acp.clone()], &r_set, false);
2653
2654 test_acp_delete!(&de_admin_rw, vec![acp], &r_set, true);
2655 }
2656
2657 macro_rules! test_acp_effective_permissions {
2658 (
2659 $ident:expr,
2660 $attrs:expr,
2661 $search_controls:expr,
2662 $modify_controls:expr,
2663 $entries:expr,
2664 $expect:expr
2665 ) => {{
2666 let ac = AccessControls::default();
2667 let mut acw = ac.write();
2668 acw.update_search($search_controls)
2669 .expect("Failed to update");
2670 acw.update_modify($modify_controls)
2671 .expect("Failed to update");
2672 let acw = acw;
2673
2674 let res = acw
2675 .effective_permission_check($ident, $attrs, $entries)
2676 .expect("Failed to apply effective_permission_check");
2677
2678 debug!("result --> {:?}", res);
2679 debug!("expect --> {:?}", $expect);
2680 assert_eq!(res, $expect);
2682 }};
2683 }
2684
2685 #[test]
2686 fn test_access_effective_permission_check_1() {
2687 sketching::test_init();
2688
2689 let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
2690
2691 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2692
2693 let r_set = vec![Arc::new(ev1)];
2694
2695 test_acp_effective_permissions!(
2696 &admin,
2697 None,
2698 vec![AccessControlSearch::from_raw(
2699 "test_acp",
2700 Uuid::new_v4(),
2701 UUID_TEST_GROUP_1,
2703 filter_valid!(f_eq(
2705 Attribute::Name,
2706 PartialValue::new_iname("testperson1")
2707 )),
2708 Attribute::Name.as_ref(),
2710 )],
2711 vec![],
2712 &r_set,
2713 vec![AccessEffectivePermission {
2714 ident: UUID_TEST_ACCOUNT_1,
2715 delete: false,
2716 target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2717 search: Access::Allow(btreeset![Attribute::Name]),
2718 modify_pres: Access::Allow(BTreeSet::new()),
2719 modify_rem: Access::Allow(BTreeSet::new()),
2720 modify_pres_class: AccessClass::Allow(BTreeSet::new()),
2721 modify_rem_class: AccessClass::Allow(BTreeSet::new()),
2722 }]
2723 )
2724 }
2725
2726 #[test]
2727 fn test_access_effective_permission_check_2() {
2728 sketching::test_init();
2729
2730 let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
2731
2732 let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
2733
2734 let r_set = vec![Arc::new(ev1)];
2735
2736 test_acp_effective_permissions!(
2737 &admin,
2738 None,
2739 vec![],
2740 vec![AccessControlModify::from_raw(
2741 "test_acp",
2742 Uuid::new_v4(),
2743 UUID_TEST_GROUP_1,
2745 filter_valid!(f_eq(
2747 Attribute::Name,
2748 PartialValue::new_iname("testperson1")
2749 )),
2750 Attribute::Name.as_ref(),
2752 Attribute::Name.as_ref(),
2753 EntryClass::Object.into(),
2754 EntryClass::Object.into(),
2755 )],
2756 &r_set,
2757 vec![AccessEffectivePermission {
2758 ident: UUID_TEST_ACCOUNT_1,
2759 delete: false,
2760 target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2761 search: Access::Allow(BTreeSet::new()),
2762 modify_pres: Access::Allow(btreeset![Attribute::Name]),
2763 modify_rem: Access::Allow(btreeset![Attribute::Name]),
2764 modify_pres_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
2765 modify_rem_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
2766 }]
2767 )
2768 }
2769
2770 #[test]
2771 fn test_access_sync_authority_create() {
2772 sketching::test_init();
2773
2774 let ce_admin = CreateEvent::new_impersonate_identity(
2775 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
2776 vec![],
2777 );
2778
2779 let ev1 = entry_init!(
2781 (Attribute::Class, EntryClass::Account.to_value()),
2782 (Attribute::Name, Value::new_iname("testperson1")),
2783 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2784 );
2785 let r1_set = vec![ev1];
2786
2787 let ev2 = entry_init!(
2788 (Attribute::Class, EntryClass::Account.to_value()),
2789 (Attribute::Class, EntryClass::SyncObject.to_value()),
2790 (Attribute::Name, Value::new_iname("testperson1")),
2791 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2792 );
2793 let r2_set = vec![ev2];
2794
2795 let acp = AccessControlCreate::from_raw(
2796 "test_create",
2797 Uuid::new_v4(),
2798 UUID_TEST_GROUP_1,
2800 filter_valid!(f_eq(
2803 Attribute::Name,
2804 PartialValue::new_iname("testperson1")
2805 )),
2806 "account sync_object",
2808 "class name uuid",
2810 );
2811
2812 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
2814 test_acp_create!(&ce_admin, vec![acp], &r2_set, false);
2816 }
2817
2818 #[test]
2819 fn test_access_sync_authority_delete() {
2820 sketching::test_init();
2821
2822 let ev1 = entry_init!(
2823 (Attribute::Class, EntryClass::Account.to_value()),
2824 (Attribute::Name, Value::new_iname("testperson1")),
2825 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2826 )
2827 .into_sealed_committed();
2828 let r1_set = vec![Arc::new(ev1)];
2829
2830 let ev2 = entry_init!(
2831 (Attribute::Class, EntryClass::Account.to_value()),
2832 (Attribute::Class, EntryClass::SyncObject.to_value()),
2833 (Attribute::Name, Value::new_iname("testperson1")),
2834 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2835 )
2836 .into_sealed_committed();
2837 let r2_set = vec![Arc::new(ev2)];
2838
2839 let de_admin = DeleteEvent::new_impersonate_entry(
2840 E_TEST_ACCOUNT_1.clone(),
2841 filter_all!(f_eq(
2842 Attribute::Name,
2843 PartialValue::new_iname("testperson1")
2844 )),
2845 );
2846
2847 let acp = AccessControlDelete::from_raw(
2848 "test_delete",
2849 Uuid::new_v4(),
2850 UUID_TEST_GROUP_1,
2852 filter_valid!(f_eq(
2854 Attribute::Name,
2855 PartialValue::new_iname("testperson1")
2856 )),
2857 );
2858
2859 test_acp_delete!(&de_admin, vec![acp.clone()], &r1_set, true);
2861 test_acp_delete!(&de_admin, vec![acp], &r2_set, false);
2863 }
2864
2865 #[test]
2866 fn test_access_sync_authority_modify() {
2867 sketching::test_init();
2868
2869 let ev1 = entry_init!(
2870 (Attribute::Class, EntryClass::Account.to_value()),
2871 (Attribute::Name, Value::new_iname("testperson1")),
2872 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2873 )
2874 .into_sealed_committed();
2875 let r1_set = vec![Arc::new(ev1)];
2876
2877 let sync_uuid = Uuid::new_v4();
2878 let ev2 = entry_init!(
2879 (Attribute::Class, EntryClass::Account.to_value()),
2880 (Attribute::Class, EntryClass::SyncObject.to_value()),
2881 (Attribute::SyncParentUuid, Value::Refer(sync_uuid)),
2882 (Attribute::Name, Value::new_iname("testperson1")),
2883 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
2884 )
2885 .into_sealed_committed();
2886 let r2_set = vec![Arc::new(ev2)];
2887
2888 let acp_allow = AccessControlModify::from_raw(
2890 "test_modify_allow",
2891 Uuid::new_v4(),
2892 UUID_TEST_GROUP_1,
2894 filter_valid!(f_eq(
2896 Attribute::Name,
2897 PartialValue::new_iname("testperson1")
2898 )),
2899 &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
2901 &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
2903 EntryClass::Account.into(),
2905 EntryClass::Account.into(),
2906 );
2907
2908 let me_pres = ModifyEvent::new_impersonate_entry(
2912 E_TEST_ACCOUNT_1.clone(),
2913 filter_all!(f_eq(
2914 Attribute::Name,
2915 PartialValue::new_iname("testperson1")
2916 )),
2917 modlist!([m_pres(
2918 Attribute::UserAuthTokenSession,
2919 &Value::new_iname("value")
2920 )]),
2921 );
2922 let me_rem = ModifyEvent::new_impersonate_entry(
2924 E_TEST_ACCOUNT_1.clone(),
2925 filter_all!(f_eq(
2926 Attribute::Name,
2927 PartialValue::new_iname("testperson1")
2928 )),
2929 modlist!([m_remove(
2930 Attribute::UserAuthTokenSession,
2931 &PartialValue::new_iname("value")
2932 )]),
2933 );
2934 let me_purge = ModifyEvent::new_impersonate_entry(
2936 E_TEST_ACCOUNT_1.clone(),
2937 filter_all!(f_eq(
2938 Attribute::Name,
2939 PartialValue::new_iname("testperson1")
2940 )),
2941 modlist!([m_purge(Attribute::UserAuthTokenSession)]),
2942 );
2943
2944 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
2946 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r1_set, true);
2948 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r1_set, true);
2950
2951 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, true);
2953 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, true);
2955 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, true);
2957
2958 let me_pres = ModifyEvent::new_impersonate_entry(
2960 E_TEST_ACCOUNT_1.clone(),
2961 filter_all!(f_eq(
2962 Attribute::Name,
2963 PartialValue::new_iname("testperson1")
2964 )),
2965 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
2966 );
2967 let me_rem = ModifyEvent::new_impersonate_entry(
2969 E_TEST_ACCOUNT_1.clone(),
2970 filter_all!(f_eq(
2971 Attribute::Name,
2972 PartialValue::new_iname("testperson1")
2973 )),
2974 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
2975 );
2976 let me_purge = ModifyEvent::new_impersonate_entry(
2978 E_TEST_ACCOUNT_1.clone(),
2979 filter_all!(f_eq(
2980 Attribute::Name,
2981 PartialValue::new_iname("testperson1")
2982 )),
2983 modlist!([m_purge(Attribute::Name)]),
2984 );
2985
2986 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
2988 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, false);
2990 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, false);
2992
2993 test_acp_modify!(
2998 &me_pres,
2999 vec![acp_allow.clone()],
3000 sync_uuid,
3001 Attribute::Name,
3002 &r2_set,
3003 true
3004 );
3005 test_acp_modify!(
3007 &me_rem,
3008 vec![acp_allow.clone()],
3009 sync_uuid,
3010 Attribute::Name,
3011 &r2_set,
3012 true
3013 );
3014 test_acp_modify!(
3016 &me_purge,
3017 vec![acp_allow],
3018 sync_uuid,
3019 Attribute::Name,
3020 &r2_set,
3021 true
3022 );
3023 }
3024
3025 #[test]
3026 fn test_access_oauth2_dyn_search() {
3027 sketching::test_init();
3028 let rs_uuid = Uuid::new_v4();
3031 let ev1 = entry_init!(
3032 (Attribute::Class, EntryClass::Object.to_value()),
3033 (
3034 Attribute::Class,
3035 EntryClass::OAuth2ResourceServer.to_value()
3036 ),
3037 (
3038 Attribute::Class,
3039 EntryClass::OAuth2ResourceServerBasic.to_value()
3040 ),
3041 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3042 (Attribute::Name, Value::new_iname("test_resource_server")),
3043 (
3044 Attribute::DisplayName,
3045 Value::new_utf8s("test_resource_server")
3046 ),
3047 (
3048 Attribute::OAuth2RsOriginLanding,
3049 Value::new_url_s("https://demo.example.com").unwrap()
3050 ),
3051 (
3052 Attribute::OAuth2RsOrigin,
3053 Value::new_url_s("app://hidden").unwrap()
3054 ),
3055 (
3056 Attribute::OAuth2RsScopeMap,
3057 Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["groups".to_string()])
3058 .expect("invalid oauthscope")
3059 ),
3060 (
3061 Attribute::OAuth2RsSupScopeMap,
3062 Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["supplement".to_string()])
3063 .expect("invalid oauthscope")
3064 ),
3065 (
3066 Attribute::OAuth2AllowInsecureClientDisablePkce,
3067 Value::new_bool(true)
3068 ),
3069 (
3070 Attribute::OAuth2JwtLegacyCryptoEnable,
3071 Value::new_bool(false)
3072 ),
3073 (Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
3074 )
3075 .into_sealed_committed();
3076
3077 let ev1_reduced = entry_init!(
3078 (Attribute::Class, EntryClass::Object.to_value()),
3079 (
3080 Attribute::Class,
3081 EntryClass::OAuth2ResourceServer.to_value()
3082 ),
3083 (
3084 Attribute::Class,
3085 EntryClass::OAuth2ResourceServerBasic.to_value()
3086 ),
3087 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3088 (Attribute::Name, Value::new_iname("test_resource_server")),
3089 (
3090 Attribute::DisplayName,
3091 Value::new_utf8s("test_resource_server")
3092 ),
3093 (
3094 Attribute::OAuth2RsOriginLanding,
3095 Value::new_url_s("https://demo.example.com").unwrap()
3096 )
3097 )
3098 .into_sealed_committed();
3099
3100 let ev2 = entry_init!(
3101 (Attribute::Class, EntryClass::Object.to_value()),
3102 (
3103 Attribute::Class,
3104 EntryClass::OAuth2ResourceServer.to_value()
3105 ),
3106 (
3107 Attribute::Class,
3108 EntryClass::OAuth2ResourceServerBasic.to_value()
3109 ),
3110 (Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
3111 (Attribute::Name, Value::new_iname("second_resource_server")),
3112 (
3113 Attribute::DisplayName,
3114 Value::new_utf8s("second_resource_server")
3115 ),
3116 (
3117 Attribute::OAuth2RsOriginLanding,
3118 Value::new_url_s("https://noaccess.example.com").unwrap()
3119 ),
3120 (
3121 Attribute::OAuth2RsOrigin,
3122 Value::new_url_s("app://hidden").unwrap()
3123 ),
3124 (
3125 Attribute::OAuth2RsScopeMap,
3126 Value::new_oauthscopemap(UUID_SYSTEM_ADMINS, btreeset!["groups".to_string()])
3127 .expect("invalid oauthscope")
3128 ),
3129 (
3130 Attribute::OAuth2RsSupScopeMap,
3131 Value::new_oauthscopemap(
3132 UUID_TEST_GROUP_1,
3134 btreeset!["supplement".to_string()]
3135 )
3136 .expect("invalid oauthscope")
3137 ),
3138 (
3139 Attribute::OAuth2AllowInsecureClientDisablePkce,
3140 Value::new_bool(true)
3141 ),
3142 (
3143 Attribute::OAuth2JwtLegacyCryptoEnable,
3144 Value::new_bool(false)
3145 ),
3146 (Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
3147 )
3148 .into_sealed_committed();
3149
3150 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
3151
3152 let se_a = SearchEvent::new_impersonate_entry(
3154 E_TEST_ACCOUNT_1.clone(),
3155 filter_all!(f_pres(Attribute::Name)),
3156 );
3157 let ex_a = vec![Arc::new(ev1)];
3158 let ex_a_reduced = vec![ev1_reduced];
3159
3160 test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
3161 test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
3162
3163 let anon: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
3165 let mut anon = anon.into_invalid_new();
3166 anon.set_ava_set(&Attribute::MemberOf, ValueSetRefer::new(UUID_TEST_GROUP_1));
3167
3168 let anon = Arc::new(anon.into_sealed_committed());
3169
3170 let se_anon =
3171 SearchEvent::new_impersonate_entry(anon, filter_all!(f_pres(Attribute::Name)));
3172 let ex_anon = vec![];
3173 test_acp_search!(&se_anon, vec![], r_set.clone(), ex_anon);
3174
3175 let se_b = SearchEvent::new_impersonate_entry(
3177 E_TEST_ACCOUNT_2.clone(),
3178 filter_all!(f_pres(Attribute::Name)),
3179 );
3180 let ex_b = vec![];
3181
3182 test_acp_search!(&se_b, vec![], r_set, ex_b);
3183 }
3184
3185 #[test]
3186 fn test_access_sync_account_dyn_search() {
3187 sketching::test_init();
3188 let sync_uuid = Uuid::new_v4();
3193 let portal_url = Url::parse("https://localhost/portal").unwrap();
3194
3195 let ev1 = entry_init!(
3196 (Attribute::Class, EntryClass::Object.to_value()),
3197 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3198 (Attribute::Uuid, Value::Uuid(sync_uuid)),
3199 (Attribute::Name, Value::new_iname("test_sync_account")),
3200 (
3201 Attribute::SyncCredentialPortal,
3202 Value::Url(portal_url.clone())
3203 )
3204 )
3205 .into_sealed_committed();
3206
3207 let ev1_reduced = entry_init!(
3208 (Attribute::Class, EntryClass::Object.to_value()),
3209 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3210 (Attribute::Uuid, Value::Uuid(sync_uuid)),
3211 (
3212 Attribute::SyncCredentialPortal,
3213 Value::Url(portal_url.clone())
3214 )
3215 )
3216 .into_sealed_committed();
3217
3218 let ev2 = entry_init!(
3219 (Attribute::Class, EntryClass::Object.to_value()),
3220 (Attribute::Class, EntryClass::SyncAccount.to_value()),
3221 (Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
3222 (Attribute::Name, Value::new_iname("test_sync_account")),
3223 (
3224 Attribute::SyncCredentialPortal,
3225 Value::Url(portal_url.clone())
3226 )
3227 )
3228 .into_sealed_committed();
3229
3230 let sync_test_account: Arc<EntrySealedCommitted> = Arc::new(
3231 entry_init!(
3232 (Attribute::Class, EntryClass::Object.to_value()),
3233 (Attribute::Class, EntryClass::Account.to_value()),
3234 (Attribute::Class, EntryClass::SyncObject.to_value()),
3235 (Attribute::Name, Value::new_iname("test_account_1")),
3236 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3237 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3238 (Attribute::SyncParentUuid, Value::Refer(sync_uuid))
3239 )
3240 .into_sealed_committed(),
3241 );
3242
3243 let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
3245
3246 let se_a = SearchEvent::new_impersonate_entry(
3247 sync_test_account,
3248 filter_all!(f_pres(Attribute::SyncCredentialPortal)),
3249 );
3250 let ex_a = vec![Arc::new(ev1)];
3251 let ex_a_reduced = vec![ev1_reduced];
3252
3253 test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
3254 test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
3255
3256 let se_b = SearchEvent::new_impersonate_entry(
3258 E_TEST_ACCOUNT_2.clone(),
3259 filter_all!(f_pres(Attribute::SyncCredentialPortal)),
3260 );
3261 let ex_b = vec![];
3262
3263 test_acp_search!(&se_b, vec![], r_set, ex_b);
3264 }
3265
3266 #[test]
3267 fn test_access_entry_managed_by_search() {
3268 sketching::test_init();
3269
3270 let test_entry = Arc::new(
3271 entry_init!(
3272 (Attribute::Class, EntryClass::Object.to_value()),
3273 (Attribute::Name, Value::new_iname("testperson1")),
3274 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3275 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3276 )
3277 .into_sealed_committed(),
3278 );
3279
3280 let data_set = vec![test_entry.clone()];
3281
3282 let se_a = SearchEvent::new_impersonate_entry(
3283 E_TEST_ACCOUNT_1.clone(),
3284 filter_all!(f_pres(Attribute::Name)),
3285 );
3286 let expect_a = vec![test_entry];
3287
3288 let se_b = SearchEvent::new_impersonate_entry(
3289 E_TEST_ACCOUNT_2.clone(),
3290 filter_all!(f_pres(Attribute::Name)),
3291 );
3292 let expect_b = vec![];
3293
3294 let acp = AccessControlSearch::from_managed_by(
3295 "test_acp",
3296 Uuid::new_v4(),
3297 AccessControlTarget::Scope(filter_valid!(f_eq(
3299 Attribute::Name,
3300 PartialValue::new_iname("testperson1")
3301 ))),
3302 Attribute::Name.as_ref(),
3305 );
3306
3307 test_acp_search!(&se_a, vec![acp.clone()], data_set.clone(), expect_a);
3309
3310 test_acp_search!(&se_b, vec![acp], data_set, expect_b);
3312 }
3313
3314 #[test]
3315 fn test_access_entry_managed_by_create() {
3316 sketching::test_init();
3317
3318 let test_entry = entry_init!(
3319 (Attribute::Class, EntryClass::Object.to_value()),
3320 (Attribute::Name, Value::new_iname("testperson1")),
3321 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3322 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3323 );
3324
3325 let data_set = vec![test_entry];
3326
3327 let ce = CreateEvent::new_impersonate_identity(
3328 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
3329 vec![],
3330 );
3331
3332 let acp = AccessControlCreate::from_managed_by(
3333 "test_create",
3334 Uuid::new_v4(),
3335 AccessControlTarget::Scope(filter_valid!(f_eq(
3336 Attribute::Name,
3337 PartialValue::new_iname("testperson1")
3338 ))),
3339 EntryClass::Account.into(),
3341 "class name uuid",
3343 );
3344
3345 test_acp_create!(&ce, vec![acp.clone()], &data_set, false);
3348 }
3349
3350 #[test]
3351 fn test_access_entry_managed_by_modify() {
3352 let test_entry = Arc::new(
3353 entry_init!(
3354 (Attribute::Class, EntryClass::Object.to_value()),
3355 (Attribute::Name, Value::new_iname("testperson1")),
3356 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3357 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3358 )
3359 .into_sealed_committed(),
3360 );
3361
3362 let data_set = vec![test_entry];
3363
3364 let me_pres = ModifyEvent::new_impersonate_entry(
3366 E_TEST_ACCOUNT_1.clone(),
3367 filter_all!(f_eq(
3368 Attribute::Name,
3369 PartialValue::new_iname("testperson1")
3370 )),
3371 modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
3372 );
3373 let me_rem = ModifyEvent::new_impersonate_entry(
3375 E_TEST_ACCOUNT_1.clone(),
3376 filter_all!(f_eq(
3377 Attribute::Name,
3378 PartialValue::new_iname("testperson1")
3379 )),
3380 modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
3381 );
3382 let me_purge = ModifyEvent::new_impersonate_entry(
3384 E_TEST_ACCOUNT_1.clone(),
3385 filter_all!(f_eq(
3386 Attribute::Name,
3387 PartialValue::new_iname("testperson1")
3388 )),
3389 modlist!([m_purge(Attribute::Name)]),
3390 );
3391
3392 let acp_allow = AccessControlModify::from_managed_by(
3393 "test_modify_allow",
3394 Uuid::new_v4(),
3395 AccessControlTarget::Scope(filter_valid!(f_eq(
3397 Attribute::Name,
3398 PartialValue::new_iname("testperson1")
3399 ))),
3400 "name class",
3402 "name class",
3404 EntryClass::Account.into(),
3406 EntryClass::Account.into(),
3407 );
3408
3409 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &data_set, true);
3411 test_acp_modify!(&me_rem, vec![acp_allow.clone()], &data_set, true);
3413 test_acp_modify!(&me_purge, vec![acp_allow.clone()], &data_set, true);
3415 }
3416
3417 #[test]
3418 fn test_access_entry_managed_by_delete() {
3419 let test_entry = Arc::new(
3420 entry_init!(
3421 (Attribute::Class, EntryClass::Object.to_value()),
3422 (Attribute::Name, Value::new_iname("testperson1")),
3423 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3424 (Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
3425 )
3426 .into_sealed_committed(),
3427 );
3428
3429 let data_set = vec![test_entry];
3430
3431 let de_a = DeleteEvent::new_impersonate_entry(
3432 E_TEST_ACCOUNT_1.clone(),
3433 filter_all!(f_eq(
3434 Attribute::Name,
3435 PartialValue::new_iname("testperson1")
3436 )),
3437 );
3438
3439 let de_b = DeleteEvent::new_impersonate_entry(
3440 E_TEST_ACCOUNT_2.clone(),
3441 filter_all!(f_eq(
3442 Attribute::Name,
3443 PartialValue::new_iname("testperson1")
3444 )),
3445 );
3446
3447 let acp = AccessControlDelete::from_managed_by(
3448 "test_delete",
3449 Uuid::new_v4(),
3450 AccessControlTarget::Scope(filter_valid!(f_eq(
3452 Attribute::Name,
3453 PartialValue::new_iname("testperson1")
3454 ))),
3455 );
3456
3457 test_acp_delete!(&de_a, vec![acp.clone()], &data_set, true);
3459 test_acp_delete!(&de_b, vec![acp], &data_set, false);
3461 }
3462
3463 #[test]
3464 fn test_access_delete_protect_system_ranges() {
3465 let ev1: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
3466 let ev1 = ev1.into_sealed_committed();
3467 let r_set = vec![Arc::new(ev1)];
3468
3469 let de_account = DeleteEvent::new_impersonate_entry(
3470 E_TEST_ACCOUNT_1.clone(),
3471 filter_all!(f_eq(
3472 Attribute::Name,
3473 PartialValue::new_iname("testperson1")
3474 )),
3475 );
3476
3477 let acp = AccessControlDelete::from_raw(
3478 "test_delete",
3479 Uuid::new_v4(),
3480 UUID_TEST_GROUP_1,
3481 filter_valid!(f_eq(Attribute::Name, PartialValue::new_iname("anonymous"))),
3483 );
3484
3485 test_acp_delete!(&de_account, vec![acp], &r_set, false);
3487 }
3488
3489 #[test]
3490 fn test_access_sync_memberof_implies_directmemberof() {
3491 sketching::test_init();
3492
3493 let ev1 = entry_init!(
3494 (Attribute::Class, EntryClass::Object.to_value()),
3495 (Attribute::Name, Value::new_iname("test_account_1")),
3496 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
3497 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3498 (Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
3499 )
3500 .into_sealed_committed();
3501 let r_set = vec![Arc::new(ev1)];
3502
3503 let exv1 = entry_init!(
3504 (Attribute::Name, Value::new_iname("test_account_1")),
3505 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
3506 (Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
3507 )
3508 .into_sealed_committed();
3509
3510 let ex_anon_some = vec![exv1];
3511
3512 let se_anon_ro = SearchEvent::new_impersonate_identity(
3513 Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
3514 filter_all!(f_pres(Attribute::Name)),
3515 );
3516
3517 let acp = AccessControlSearch::from_raw(
3518 "test_acp",
3519 Uuid::new_v4(),
3520 UUID_TEST_GROUP_1,
3522 filter_valid!(f_eq(
3524 Attribute::Uuid,
3525 PartialValue::Uuid(UUID_TEST_ACCOUNT_1)
3526 )),
3527 format!("{} {}", Attribute::Name, Attribute::MemberOf).as_str(),
3530 );
3531
3532 test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
3534 }
3535
3536 #[test]
3537 fn test_access_protected_deny_create() {
3538 sketching::test_init();
3539
3540 let ev1 = entry_init!(
3541 (Attribute::Class, EntryClass::Account.to_value()),
3542 (Attribute::Name, Value::new_iname("testperson1")),
3543 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3544 );
3545 let r1_set = vec![ev1];
3546
3547 let ev2 = entry_init!(
3548 (Attribute::Class, EntryClass::Account.to_value()),
3549 (Attribute::Class, EntryClass::System.to_value()),
3550 (Attribute::Name, Value::new_iname("testperson1")),
3551 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3552 );
3553
3554 let r2_set = vec![ev2];
3555
3556 let ce_admin = CreateEvent::new_impersonate_identity(
3557 Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
3558 vec![],
3559 );
3560
3561 let acp = AccessControlCreate::from_raw(
3562 "test_create",
3563 Uuid::new_v4(),
3564 UUID_TEST_GROUP_1,
3566 filter_valid!(f_eq(
3569 Attribute::Name,
3570 PartialValue::new_iname("testperson1")
3571 )),
3572 EntryClass::Account.into(),
3574 "class name uuid",
3576 );
3577
3578 test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
3580 test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
3582 }
3583
3584 #[test]
3585 fn test_access_protected_deny_delete() {
3586 sketching::test_init();
3587
3588 let ev1 = entry_init!(
3589 (Attribute::Class, EntryClass::Account.to_value()),
3590 (Attribute::Name, Value::new_iname("testperson1")),
3591 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3592 )
3593 .into_sealed_committed();
3594 let r1_set = vec![Arc::new(ev1)];
3595
3596 let ev2 = entry_init!(
3597 (Attribute::Class, EntryClass::Account.to_value()),
3598 (Attribute::Class, EntryClass::System.to_value()),
3599 (Attribute::Name, Value::new_iname("testperson1")),
3600 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3601 )
3602 .into_sealed_committed();
3603
3604 let r2_set = vec![Arc::new(ev2)];
3605
3606 let de = DeleteEvent::new_impersonate_entry(
3607 E_TEST_ACCOUNT_1.clone(),
3608 filter_all!(f_eq(
3609 Attribute::Name,
3610 PartialValue::new_iname("testperson1")
3611 )),
3612 );
3613
3614 let acp = AccessControlDelete::from_raw(
3615 "test_delete",
3616 Uuid::new_v4(),
3617 UUID_TEST_GROUP_1,
3619 filter_valid!(f_eq(
3621 Attribute::Name,
3622 PartialValue::new_iname("testperson1")
3623 )),
3624 );
3625
3626 test_acp_delete!(&de, vec![acp.clone()], &r1_set, true);
3628 test_acp_delete!(&de, vec![acp.clone()], &r2_set, false);
3630 }
3631
3632 #[test]
3633 fn test_access_protected_deny_modify() {
3634 sketching::test_init();
3635
3636 let ev1 = entry_init!(
3637 (Attribute::Class, EntryClass::Account.to_value()),
3638 (Attribute::Name, Value::new_iname("testperson1")),
3639 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3640 )
3641 .into_sealed_committed();
3642 let r1_set = vec![Arc::new(ev1)];
3643
3644 let ev2 = entry_init!(
3645 (Attribute::Class, EntryClass::Account.to_value()),
3646 (Attribute::Class, EntryClass::System.to_value()),
3647 (Attribute::Name, Value::new_iname("testperson1")),
3648 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
3649 )
3650 .into_sealed_committed();
3651
3652 let r2_set = vec![Arc::new(ev2)];
3653
3654 let acp_allow = AccessControlModify::from_raw(
3656 "test_modify_allow",
3657 Uuid::new_v4(),
3658 UUID_TEST_GROUP_1,
3660 filter_valid!(f_eq(
3662 Attribute::Name,
3663 PartialValue::new_iname("testperson1")
3664 )),
3665 "displayname class",
3667 "displayname class",
3669 "system recycled",
3671 "system recycled",
3672 );
3673
3674 let me_pres = ModifyEvent::new_impersonate_entry(
3675 E_TEST_ACCOUNT_1.clone(),
3676 filter_all!(f_eq(
3677 Attribute::Name,
3678 PartialValue::new_iname("testperson1")
3679 )),
3680 modlist!([m_pres(Attribute::DisplayName, &Value::new_utf8s("value"))]),
3681 );
3682
3683 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
3685
3686 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
3688
3689 let me_rem_sys = ModifyEvent::new_impersonate_entry(
3691 E_TEST_ACCOUNT_1.clone(),
3692 filter_all!(f_eq(
3693 Attribute::Class,
3694 PartialValue::new_iname("testperson1")
3695 )),
3696 modlist!([m_remove(
3697 Attribute::Class,
3698 &EntryClass::System.to_partialvalue()
3699 )]),
3700 );
3701
3702 test_acp_modify!(&me_rem_sys, vec![acp_allow.clone()], &r2_set, false);
3703
3704 let me_pres = ModifyEvent::new_impersonate_entry(
3706 E_TEST_ACCOUNT_1.clone(),
3707 filter_all!(f_eq(
3708 Attribute::Name,
3709 PartialValue::new_iname("testperson1")
3710 )),
3711 modlist!([m_pres(Attribute::Class, &EntryClass::Recycled.to_value())]),
3712 );
3713
3714 test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, false);
3715 }
3716}