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