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