kanidmd_lib/server/access/
mod.rs

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