Skip to main content

kanidmd_lib/server/access/
mod.rs

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