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