kanidm 1.1.0-alpha

Kanidm Server Library and Binary
Documentation
// Access Control Profiles
//
// This is a pretty important and security sensitive part of the code - it's
// responsible for making sure that who is allowed to do what is enforced, as
// well as who is *not* allowed to do what.
//
// A detailed design can be found in access-profiles-and-security.

//
// This part of the server really has a few parts
// - the ability to parse access profile structures into real ACP structs
// - the ability to apply sets of ACP's to entries for coarse actions (IE
//   search.
// - the ability to turn an entry into a partial-entry for results send
//   requirements (also search).
//

// use concread::collections::bptree::*;
use concread::cowcell::*;
use kanidm_proto::v1::Filter as ProtoFilter;
use kanidm_proto::v1::OperationError;
use std::collections::BTreeSet;
// use hashbrown::HashSet;
use std::ops::DerefMut;
use uuid::Uuid;

use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryInit, EntryNew, EntryReduced, EntrySealed};
use crate::filter::{Filter, FilterValid};
use crate::modify::Modify;
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::PartialValue;

use crate::event::{CreateEvent, DeleteEvent, EventOrigin, ModifyEvent, SearchEvent};

lazy_static! {
    static ref CLASS_ACS: PartialValue = PartialValue::new_class("access_control_search");
    static ref CLASS_ACC: PartialValue = PartialValue::new_class("access_control_create");
    static ref CLASS_ACD: PartialValue = PartialValue::new_class("access_control_delete");
    static ref CLASS_ACM: PartialValue = PartialValue::new_class("access_control_modify");
    static ref CLASS_ACP: PartialValue = PartialValue::new_class("access_control_profile");
}

// =========================================================================
// PARSE ENTRY TO ACP, AND ACP MANAGEMENT
// =========================================================================

#[derive(Debug, Clone)]
pub struct AccessControlSearch {
    acp: AccessControlProfile,
    attrs: BTreeSet<String>,
}

impl AccessControlSearch {
    pub fn try_from(
        audit: &mut AuditScope,
        qs: &mut QueryServerWriteTransaction,
        value: &Entry<EntrySealed, EntryCommitted>,
    ) -> Result<Self, OperationError> {
        if !value.attribute_value_pres("class", &CLASS_ACS) {
            ladmin_error!(audit, "class access_control_search not present.");
            return Err(OperationError::InvalidACPState(
                "Missing access_control_search".to_string(),
            ));
        }

        let attrs = value
            .get_ava_as_str("acp_search_attr")
            .ok_or_else(|| {
                ladmin_error!(audit, "Missing acp_search_attr");
                OperationError::InvalidACPState("Missing acp_search_attr".to_string())
            })?
            .map(|s| s.to_string())
            .collect();

        let acp = AccessControlProfile::try_from(audit, qs, value)?;

        Ok(AccessControlSearch { acp, attrs })
    }

    #[cfg(test)]
    unsafe fn from_raw(
        name: &str,
        uuid: &str,
        receiver: Filter<FilterValid>,
        targetscope: Filter<FilterValid>,
        attrs: &str,
    ) -> Self {
        AccessControlSearch {
            acp: AccessControlProfile {
                name: name.to_string(),
                uuid: Uuid::parse_str(uuid).unwrap(),
                receiver,
                targetscope,
            },
            attrs: attrs.split_whitespace().map(|s| s.to_string()).collect(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct AccessControlDelete {
    acp: AccessControlProfile,
}

impl AccessControlDelete {
    pub fn try_from(
        audit: &mut AuditScope,
        qs: &mut QueryServerWriteTransaction,
        value: &Entry<EntrySealed, EntryCommitted>,
    ) -> Result<Self, OperationError> {
        if !value.attribute_value_pres("class", &CLASS_ACD) {
            ladmin_error!(audit, "class access_control_delete not present.");
            return Err(OperationError::InvalidACPState(
                "Missing access_control_delete".to_string(),
            ));
        }

        Ok(AccessControlDelete {
            acp: AccessControlProfile::try_from(audit, qs, value)?,
        })
    }

    #[cfg(test)]
    unsafe fn from_raw(
        name: &str,
        uuid: &str,
        receiver: Filter<FilterValid>,
        targetscope: Filter<FilterValid>,
    ) -> Self {
        AccessControlDelete {
            acp: AccessControlProfile {
                name: name.to_string(),
                uuid: Uuid::parse_str(uuid).unwrap(),
                receiver,
                targetscope,
            },
        }
    }
}

#[derive(Debug, Clone)]
pub struct AccessControlCreate {
    acp: AccessControlProfile,
    classes: Vec<String>,
    attrs: Vec<String>,
}

impl AccessControlCreate {
    pub fn try_from(
        audit: &mut AuditScope,
        qs: &mut QueryServerWriteTransaction,
        value: &Entry<EntrySealed, EntryCommitted>,
    ) -> Result<Self, OperationError> {
        if !value.attribute_value_pres("class", &CLASS_ACC) {
            ladmin_error!(audit, "class access_control_create not present.");
            return Err(OperationError::InvalidACPState(
                "Missing access_control_create".to_string(),
            ));
        }

        let attrs = value
            .get_ava_as_str("acp_create_attr")
            .map(|i| i.map(|s| s.to_string()).collect())
            .unwrap_or_else(Vec::new);

        let classes = value
            .get_ava_as_str("acp_create_class")
            .map(|i| i.map(|s| s.to_string()).collect())
            .unwrap_or_else(Vec::new);

        Ok(AccessControlCreate {
            acp: AccessControlProfile::try_from(audit, qs, value)?,
            classes,
            attrs,
        })
    }

    #[cfg(test)]
    unsafe fn from_raw(
        name: &str,
        uuid: &str,
        receiver: Filter<FilterValid>,
        targetscope: Filter<FilterValid>,
        classes: &str,
        attrs: &str,
    ) -> Self {
        AccessControlCreate {
            acp: AccessControlProfile {
                name: name.to_string(),
                uuid: Uuid::parse_str(uuid).unwrap(),
                receiver,
                targetscope,
            },
            classes: classes.split_whitespace().map(|s| s.to_string()).collect(),
            attrs: attrs.split_whitespace().map(|s| s.to_string()).collect(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct AccessControlModify {
    acp: AccessControlProfile,
    classes: Vec<String>,
    presattrs: Vec<String>,
    remattrs: Vec<String>,
}

impl AccessControlModify {
    pub fn try_from(
        audit: &mut AuditScope,
        qs: &mut QueryServerWriteTransaction,
        value: &Entry<EntrySealed, EntryCommitted>,
    ) -> Result<Self, OperationError> {
        if !value.attribute_value_pres("class", &CLASS_ACM) {
            ladmin_error!(audit, "class access_control_modify not present.");
            return Err(OperationError::InvalidACPState(
                "Missing access_control_modify".to_string(),
            ));
        }

        let presattrs = value
            .get_ava_as_str("acp_modify_presentattr")
            .map(|i| i.map(|s| s.to_string()).collect())
            .unwrap_or_else(Vec::new);

        let remattrs = value
            .get_ava_as_str("acp_modify_removedattr")
            .map(|i| i.map(|s| s.to_string()).collect())
            .unwrap_or_else(Vec::new);

        let classes = value
            .get_ava_as_str("acp_modify_class")
            .map(|i| i.map(|s| s.to_string()).collect())
            .unwrap_or_else(Vec::new);

        Ok(AccessControlModify {
            acp: AccessControlProfile::try_from(audit, qs, value)?,
            classes,
            presattrs,
            remattrs,
        })
    }

    #[cfg(test)]
    unsafe fn from_raw(
        name: &str,
        uuid: &str,
        receiver: Filter<FilterValid>,
        targetscope: Filter<FilterValid>,
        presattrs: &str,
        remattrs: &str,
        classes: &str,
    ) -> Self {
        AccessControlModify {
            acp: AccessControlProfile {
                name: name.to_string(),
                uuid: Uuid::parse_str(uuid).unwrap(),
                receiver,
                targetscope,
            },
            classes: classes.split_whitespace().map(|s| s.to_string()).collect(),
            presattrs: presattrs
                .split_whitespace()
                .map(|s| s.to_string())
                .collect(),
            remattrs: remattrs.split_whitespace().map(|s| s.to_string()).collect(),
        }
    }
}

#[derive(Debug, Clone)]
struct AccessControlProfile {
    name: String,
    uuid: Uuid,
    receiver: Filter<FilterValid>,
    targetscope: Filter<FilterValid>,
}

impl AccessControlProfile {
    fn try_from(
        audit: &mut AuditScope,
        qs: &mut QueryServerWriteTransaction,
        value: &Entry<EntrySealed, EntryCommitted>,
    ) -> Result<Self, OperationError> {
        // Assert we have class access_control_profile
        if !value.attribute_value_pres("class", &CLASS_ACP) {
            ladmin_error!(audit, "class access_control_profile not present.");
            return Err(OperationError::InvalidACPState(
                "Missing access_control_profile".to_string(),
            ));
        }

        // copy name
        let name = value
            .get_ava_single_str("name")
            .ok_or_else(|| {
                ladmin_error!(audit, "Missing name");
                OperationError::InvalidACPState("Missing name".to_string())
            })?
            .to_string();
        // copy uuid
        let uuid = *value.get_uuid();
        // receiver, and turn to real filter
        let receiver_f: ProtoFilter = value
            .get_ava_single_protofilter("acp_receiver")
            // .map(|pf| pf.clone())
            .cloned()
            .ok_or_else(|| {
                ladmin_error!(audit, "Missing acp_receiver");
                OperationError::InvalidACPState("Missing acp_receiver".to_string())
            })?;
        // targetscope, and turn to real filter
        let targetscope_f: ProtoFilter = value
            .get_ava_single_protofilter("acp_targetscope")
            // .map(|pf| pf.clone())
            .cloned()
            .ok_or_else(|| {
                ladmin_error!(audit, "Missing acp_targetscope");
                OperationError::InvalidACPState("Missing acp_targetscope".to_string())
            })?;

        let receiver_i = Filter::from_rw(audit, &receiver_f, qs).map_err(|e| {
            ladmin_error!(audit, "Receiver validation failed {:?}", e);
            e
        })?;
        let receiver = receiver_i.validate(qs.get_schema()).map_err(|e| {
            ladmin_error!(audit, "acp_receiver Schema Violation {:?}", e);
            OperationError::SchemaViolation(e)
        })?;

        let targetscope_i = Filter::from_rw(audit, &targetscope_f, qs).map_err(|e| {
            ladmin_error!(audit, "Targetscope validation failed {:?}", e);
            e
        })?;

        let targetscope = targetscope_i.validate(qs.get_schema()).map_err(|e| {
            ladmin_error!(audit, "acp_targetscope Schema Violation {:?}", e);
            OperationError::SchemaViolation(e)
        })?;

        Ok(AccessControlProfile {
            name,
            uuid,
            receiver,
            targetscope,
        })
    }
}

// =========================================================================
// ACP transactions and management for server bits.
// =========================================================================

#[derive(Clone)]
struct AccessControlsInner {
    acps_search: Vec<AccessControlSearch>,
    acps_create: Vec<AccessControlCreate>,
    acps_modify: Vec<AccessControlModify>,
    acps_delete: Vec<AccessControlDelete>,
}

pub struct AccessControls {
    inner: CowCell<AccessControlsInner>,
}

pub trait AccessControlsTransaction {
    fn get_search(&self) -> &Vec<AccessControlSearch>;
    fn get_create(&self) -> &Vec<AccessControlCreate>;
    fn get_modify(&self) -> &Vec<AccessControlModify>;
    fn get_delete(&self) -> &Vec<AccessControlDelete>;

    // Contains all the way to eval acps to entries
    fn search_filter_entries(
        &self,
        audit: &mut AuditScope,
        se: &SearchEvent,
        entries: Vec<Entry<EntrySealed, EntryCommitted>>,
    ) -> Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> {
        // If this is an internal search, return our working set.
        let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &se.event.origin {
            EventOrigin::Internal => {
                ltrace!(audit, "Internal operation, bypassing access check");
                // No need to check ACS
                return Ok(entries);
            }
            EventOrigin::User(e) => &e,
        };
        lperf_segment!(audit, "access::search_filter_entries", || {
            lsecurity_access!(audit, "Access check for event: {:?}", se);
            // Some useful references we'll use for the remainder of the operation
            let search_state = self.get_search();

            // First get the set of acps that apply to this receiver
            let related_acp: Vec<&AccessControlSearch> =
                lperf_segment!(audit, "access::search_filter_entries<related_acp>", || {
                    search_state
                        .iter()
                        // .filter_map(|(_, acs)| {
                        .filter(|acs| {
                            // Now resolve the receiver filter
                            // Okay, so in filter resolution, the primary error case
                            // is that we have a non-user in the event. We have already
                            // checked for this above BUT we should still check here
                            // properly just in case.
                            //
                            // In this case, we assume that if the event is internal
                            // that the receiver can NOT match because it has no selfuuid
                            // and can as a result, never return true. This leads to this
                            // acp not being considered in that case ... which should never
                            // happen because we already bypassed internal ops above!
                            //
                            // A possible solution is to change the filter resolve function
                            // such that it takes an entry, rather than an event, but that
                            // would create issues in search.
                            let f_val = acs.acp.receiver.clone();
                            match f_val.resolve(&se.event, None) {
                                Ok(f_res) => rec_entry.entry_match_no_index(&f_res),
                                Err(e) => {
                                    ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                    false
                                }
                            }
                        })
                        .collect()
                });

            related_acp.iter().for_each(|racp| {
                lsecurity_access!(audit, "Related acs -> {:?}", racp.acp.name);
            });

            // Get the set of attributes requested by this se filter. This is what we are
            // going to access check.
            let requested_attrs: BTreeSet<&str> = se.filter_orig.get_attr_set();

            // For each entry
            let allowed_entries: Vec<Entry<EntrySealed, EntryCommitted>> = lperf_segment!(
                audit,
                "access::search_filter_entries<allowed_entries>",
                || {
                    entries
                        .into_iter()
                        .filter(|e| {
                            // For each acp
                            let allowed_attrs: BTreeSet<&str> = related_acp
                                .iter()
                                .filter_map(|acs| {
                                    let f_val = acs.acp.targetscope.clone();
                                    match f_val.resolve(&se.event, None) {
                                        Ok(f_res) => {
                                            // if it applies
                                            if e.entry_match_no_index(&f_res) {
                                                lsecurity_access!(
                                                    audit,
                                                    "entry {:?} matches acs {:?}",
                                                    e.get_uuid(),
                                                    acs
                                                );
                                                // add search_attrs to allowed.
                                                Some(acs.attrs.iter().map(|s| s.as_str()))
                                            } else {
                                                lsecurity_access!(
                                                    audit,
                                                    "entry {:?} DOES NOT match acs {:?}",
                                                    e.get_uuid(),
                                                    acs
                                                );
                                                None
                                            }
                                        }
                                        Err(e) => {
                                            ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                            None
                                        }
                                    }
                                })
                                .flatten()
                                .collect();

                            lsecurity_access!(audit, "-- for entry         --> {:?}", e.get_uuid());
                            lsecurity_access!(
                                audit,
                                "allowed attributes   --> {:?}",
                                allowed_attrs
                            );
                            lsecurity_access!(
                                audit,
                                "requested attributes --> {:?}",
                                requested_attrs
                            );

                            // is attr set a subset of allowed set?
                            // true -> entry is allowed in result set
                            // false -> the entry is not allowed to be searched by this entity, so is
                            //          excluded.
                            let decision = requested_attrs.is_subset(&allowed_attrs);
                            lsecurity_access!(audit, "search attr decision --> {:?}", decision);
                            decision
                        })
                        .collect()
                }
            );

            if allowed_entries.is_empty() {
                lsecurity_access!(audit, "denied ❌");
            } else {
                lsecurity_access!(audit, "allowed {} entries ✅", allowed_entries.len());
            }

            Ok(allowed_entries)
        })
    }

    fn search_filter_entry_attributes(
        &self,
        audit: &mut AuditScope,
        se: &SearchEvent,
        entries: Vec<Entry<EntrySealed, EntryCommitted>>,
    ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
        // If this is an internal search, do nothing. This can occur in some test cases ONLY
        let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &se.event.origin {
            EventOrigin::Internal => {
                if cfg!(test) {
                    ltrace!(audit, "TEST: Internal search in external interface - allowing due to cfg test ...");
                    // In tests we just push everything back.
                    return Ok(entries
                        .into_iter()
                        .map(|e| unsafe { e.into_reduced() })
                        .collect());
                } else {
                    // In production we can't risk leaking data here, so we return
                    // empty sets.
                    lsecurity_critical!(audit, "IMPOSSIBLE STATE: Internal search in external interface?! Returning empty for safety.");
                    // No need to check ACS
                    return Ok(Vec::new());
                }
            }
            EventOrigin::User(e) => &e,
        };
        lperf_segment!(audit, "access::search_filter_entry_attributes", || {
            /*
             * Super similar to above (could even re-use some parts). Given a set of entries,
             * reduce the attribute sets on them to "what is visible". This is ONLY called on
             * the server edge, such that clients only see what they can, but internally,
             * impersonate and such actually still get the whole entry back as not to break
             * modify and co.
             */
            lsecurity_access!(audit, "Access check and reduce for event: {:?}", se);

            // Some useful references we'll use for the remainder of the operation
            let search_state = self.get_search();

            // Get the relevant acps for this receiver.
            let related_acp: Vec<&AccessControlSearch> = lperf_segment!(
                audit,
                "access::search_filter_entry_attributes<related_acp>",
                || {
                    search_state
                        .iter()
                        // .filter_map(|(_, acs)| {
                        .filter(|acs| {
                            let f_val = acs.acp.receiver.clone();
                            match f_val.resolve(&se.event, None) {
                                Ok(f_res) => {
                                    // Is our user covered by this acs?
                                    if rec_entry.entry_match_no_index(&f_res) {
                                        // If so, let's check if the attr request is relevant.
                                        match &se.attrs {
                                            Some(r_attrs) => {
                                                // If we have a requested attr set, are any of them
                                                // in the attrs this acs covers?
                                                //
                                                // is disjoint sees if there is an overlap - we need
                                                // not disjoint because if there is overlap, then this
                                                // must be a relevant acp
                                                !acs.attrs.is_disjoint(r_attrs)
                                            }
                                            // All attrs requested, it must be relevant.
                                            None => true,
                                        }
                                    } else {
                                        false
                                    }
                                }
                                Err(e) => {
                                    ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                    false
                                }
                            }
                        })
                        .collect()
                }
            );

            related_acp.iter().for_each(|racp| {
                lsecurity_access!(audit, "Related acs -> {:?}", racp.acp.name);
            });

            // Build a reference set from the req_attrs. This is what we test against
            // to see if the attribute is something we currently want.
            let req_attrs: Option<BTreeSet<_>> = se
                .attrs
                .as_ref()
                .map(|vs| vs.iter().map(|s| s.as_str()).collect());

            //  For each entry
            let allowed_entries: Vec<Entry<EntryReduced, EntryCommitted>> = lperf_segment!(
                audit,
                "access::search_filter_entry_attributes<allowed_entries>",
                || {
                    entries
                        .into_iter()
                        .map(|e| {
                            // Get the set of attributes you can see for this entry
                            // this is within your related acp scope.
                            let allowed_attrs: BTreeSet<&str> = related_acp
                                .iter()
                                .filter_map(|acs| {
                                    let f_val = acs.acp.targetscope.clone();
                                    match f_val.resolve(&se.event, None) {
                                        Ok(f_res) => {
                                            // if it applies
                                            if e.entry_match_no_index(&f_res) {
                                                lsecurity_access!(
                                                    audit,
                                                    "entry {:?} matches acs {:?}",
                                                    e.get_uuid(),
                                                    acs
                                                );
                                                // add search_attrs to allowed iterator
                                                Some(acs.attrs.iter().map(|s| s.as_str()).filter(
                                                    |s| {
                                                        match &req_attrs {
                                                            // We return all as we requested all.
                                                            None => true,
                                                            Some(r_attrs) => {
                                                                // If we have a req_attrs set, we only return
                                                                // things that were requested.
                                                                r_attrs.contains(s)
                                                            }
                                                        }
                                                    },
                                                ))
                                            } else {
                                                lsecurity_access!(
                                                    audit,
                                                    "entry {:?} DOES NOT match acs {:?}",
                                                    e.get_uuid(),
                                                    acs
                                                );
                                                None
                                            }
                                        }
                                        Err(e) => {
                                            ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                            None
                                        }
                                    }
                                })
                                .flatten()
                                .collect();

                            // Remove all others that are present on the entry.
                            lsecurity_access!(audit, "-- for entry         --> {:?}", e.get_uuid());
                            lsecurity_access!(audit, "requested attributes --> {:?}", req_attrs);
                            lsecurity_access!(
                                audit,
                                "allowed attributes   --> {:?}",
                                allowed_attrs
                            );

                            // Remove anything that wasn't requested.
                            // THIS IS NOW DONE IN THE ITERATOR
                            /*
                            let f_allowed_attrs: BTreeSet<&str> = match &req_attrs {
                                Some(v) => allowed_attrs.intersection(&v).copied().collect(),
                                None => allowed_attrs,
                            };
                            */

                            // Now purge the attrs that are NOT in this.
                            e.reduce_attributes(allowed_attrs)
                        })
                        .collect()
                }
            );

            if allowed_entries.is_empty() {
                lsecurity_access!(audit, "reduced to empty set on all entries ❌");
            } else {
                lsecurity_access!(
                    audit,
                    "attribute set reduced on {} entries ✅",
                    allowed_entries.len()
                );
            }

            Ok(allowed_entries)
        })
    }

    fn modify_allow_operation(
        &self,
        audit: &mut AuditScope,
        me: &ModifyEvent,
        entries: &[Entry<EntrySealed, EntryCommitted>],
    ) -> Result<bool, OperationError> {
        let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &me.event.origin {
            EventOrigin::Internal => {
                ltrace!(audit, "Internal operation, bypassing access check");
                // No need to check ACS
                return Ok(true);
            }
            EventOrigin::User(e) => &e,
        };
        lperf_segment!(audit, "access::modify_allow_operation", || {
            lsecurity_access!(audit, "Access check for event: {:?}", me);

            // Some useful references we'll use for the remainder of the operation
            let modify_state = self.get_modify();

            // Pre-check if the no-no purge class is present
            let disallow = me.modlist.iter().fold(false, |acc, m| {
                if acc {
                    acc
                } else {
                    match m {
                        Modify::Purged(a) => a == "class",
                        _ => false,
                    }
                }
            });
            if disallow {
                lsecurity_access!(audit, "Disallowing purge class in modification");
                return Ok(false);
            }

            // Find the acps that relate to the caller.
            let related_acp: Vec<&AccessControlModify> = modify_state
                .iter()
                .filter(|acs| {
                    let f_val = acs.acp.receiver.clone();
                    match f_val.resolve(&me.event, None) {
                        Ok(f_res) => rec_entry.entry_match_no_index(&f_res),
                        Err(e) => {
                            ladmin_error!(
                                audit,
                                "A internal filter was passed for resolution!?!? {:?}",
                                e
                            );
                            false
                        }
                    }
                })
                .collect();

            related_acp.iter().for_each(|racp| {
                lsecurity_access!(audit, "Related acs -> {:?}", racp.acp.name);
            });

            // build two sets of "requested pres" and "requested rem"
            let requested_pres: BTreeSet<&str> = me
                .modlist
                .iter()
                .filter_map(|m| match m {
                    Modify::Present(a, _) => Some(a.as_str()),
                    _ => None,
                })
                .collect();

            let requested_rem: BTreeSet<&str> = me
                .modlist
                .iter()
                .filter_map(|m| match m {
                    Modify::Removed(a, _) => Some(a.as_str()),
                    Modify::Purged(a) => Some(a.as_str()),
                    _ => None,
                })
                .collect();

            // Build the set of classes that we to work on, only in terms of "addition". To remove
            // I think we have no limit, but ... william of the future may find a problem with this
            // policy.
            let requested_classes: BTreeSet<&str> = me
                .modlist
                .iter()
                .filter_map(|m| match m {
                    Modify::Present(a, v) => {
                        if a.as_str() == "class" {
                            // Here we have an option<&str> which could mean there is a risk of
                            // a malicious entity attempting to trick us by masking class mods
                            // in non-iutf8 types. However, the server first won't respect their
                            // existance, and second, we would have failed the mod at schema checking
                            // earlier in the process as these were not correctly type. As a result
                            // we can trust these to be correct here and not to be "None".
                            Some(v.to_str_unwrap())
                        } else {
                            None
                        }
                    }
                    Modify::Removed(a, v) => {
                        if a.as_str() == "class" {
                            Some(v.to_str_unwrap())
                        } else {
                            None
                        }
                    }
                    _ => None,
                })
                .collect();

            lsecurity_access!(audit, "Requested present set: {:?}", requested_pres);
            lsecurity_access!(audit, "Requested remove set: {:?}", requested_rem);
            lsecurity_access!(audit, "Requested class set: {:?}", requested_classes);

            let r = entries.iter().fold(true, |acc, e| {
                if !acc {
                    false
                } else {
                    // For this entry, find the acp's that apply to it from the
                    // set that apply to the entry that is performing the operation
                    let scoped_acp: Vec<&AccessControlModify> = related_acp
                        .iter()
                        .filter_map(|acm: &&AccessControlModify| {
                            // We are continually compiling and using these
                            // in a tight loop, so this is a possible oppurtunity
                            // to cache or handle these filters better - filter compiler
                            // cache maybe?
                            let f_val = acm.acp.targetscope.clone();
                            match f_val.resolve(&me.event, None) {
                                Ok(f_res) => {
                                    if e.entry_match_no_index(&f_res) {
                                        Some(*acm)
                                    } else {
                                        None
                                    }
                                }
                                Err(e) => {
                                    ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                    None
                                }
                            }
                        })
                        .collect();
                    // Build the sets of classes, pres and rem we are allowed to modify, extend
                    // or use based on the set of matched acps.
                    let allowed_pres: BTreeSet<&str> = scoped_acp
                        .iter()
                        .flat_map(|acp| acp.presattrs.iter().map(|v| v.as_str()))
                        .collect();

                    let allowed_rem: BTreeSet<&str> = scoped_acp
                        .iter()
                        .flat_map(|acp| acp.remattrs.iter().map(|v| v.as_str()))
                        .collect();

                    let allowed_classes: BTreeSet<&str> = scoped_acp
                        .iter()
                        .flat_map(|acp| acp.classes.iter().map(|v| v.as_str()))
                        .collect();

                    // Now check all the subsets are true. Remember, purge class
                    // is already checked above.
                    if !requested_pres.is_subset(&allowed_pres) {
                        lsecurity_access!(audit, "requested_pres is not a subset of allowed");
                        lsecurity_access!(audit, "{:?} !⊆ {:?}", requested_pres, allowed_pres);
                        false
                    } else if !requested_rem.is_subset(&allowed_rem) {
                        lsecurity_access!(audit, "requested_rem is not a subset of allowed");
                        lsecurity_access!(audit, "{:?} !⊆ {:?}", requested_rem, allowed_rem);
                        false
                    } else if !requested_classes.is_subset(&allowed_classes) {
                        lsecurity_access!(audit, "requested_classes is not a subset of allowed");
                        lsecurity_access!(
                            audit,
                            "{:?} !⊆ {:?}",
                            requested_classes,
                            allowed_classes
                        );
                        false
                    } else {
                        lsecurity_access!(audit, "passed pres, rem, classes check.");
                        true
                    }
                } // if acc == false
            });
            if r {
                lsecurity_access!(audit, "allowed ✅");
            } else {
                lsecurity_access!(audit, "denied ❌");
            }
            Ok(r)
        })
    }

    fn create_allow_operation(
        &self,
        audit: &mut AuditScope,
        ce: &CreateEvent,
        entries: &[Entry<EntryInit, EntryNew>],
    ) -> Result<bool, OperationError> {
        let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &ce.event.origin {
            EventOrigin::Internal => {
                ltrace!(audit, "Internal operation, bypassing access check");
                // No need to check ACS
                return Ok(true);
            }
            EventOrigin::User(e) => &e,
        };
        lperf_segment!(audit, "access::create_allow_operation", || {
            lsecurity_access!(audit, "Access check for event: {:?}", ce);

            // Some useful references we'll use for the remainder of the operation
            let create_state = self.get_create();

            // Find the acps that relate to the caller.
            let related_acp: Vec<&AccessControlCreate> = create_state
                .iter()
                .filter(|acs| {
                    let f_val = acs.acp.receiver.clone();
                    match f_val.resolve(&ce.event, None) {
                        Ok(f_res) => rec_entry.entry_match_no_index(&f_res),
                        Err(e) => {
                            ladmin_error!(
                                audit,
                                "A internal filter was passed for resolution!?!? {:?}",
                                e
                            );
                            false
                        }
                    }
                })
                .collect();

            lsecurity_access!(audit, "Related acc -> {:?}", related_acp);

            // For each entry
            let r = entries.iter().fold(true, |acc, e| {
                if !acc {
                    // We have already failed, move on.
                    false
                } else {
                    // Build the set of requested classes and attrs here.
                    let create_attrs: BTreeSet<&str> = e.get_ava_names().collect();
                    // If this is empty, we make an empty set, which is fine because
                    // the empty class set despite matching is_subset, will have the
                    // following effect:
                    // * there is no class on entry, so schema will fail
                    // * plugin-base will add object to give a class, but excess
                    //   attrs will cause fail (could this be a weakness?)
                    // * class is a "may", so this could be empty in the rules, so
                    //   if the accr is empty this would not be a true subset,
                    //   so this would "fail", but any content in the accr would
                    //   have to be validated.
                    //
                    // I still think if this is None, we should just fail here ...
                    // because it shouldn't be possible to match.

                    let create_classes: BTreeSet<&str> = match e.get_ava_as_str("class") {
                        Some(s) => s.collect(),
                        None => {
                            ladmin_error!(audit, "Class set failed to build - corrupted entry?");
                            return false;
                        }
                    };

                    related_acp.iter().fold(false, |r_acc, accr| {
                        if r_acc {
                            // Already allowed, continue.
                            r_acc
                        } else {
                            // Check to see if allowed.
                            let f_val = accr.acp.targetscope.clone();
                            match f_val.resolve(&ce.event, None) {
                                Ok(f_res) => {
                                    if e.entry_match_no_index(&f_res) {
                                        lsecurity_access!(
                                            audit,
                                            "entry {:?} matches acs {:?}",
                                            e,
                                            accr
                                        );
                                        // It matches, so now we have to check attrs and classes.
                                        // Remember, we have to match ALL requested attrs
                                        // and classes to pass!
                                        let allowed_attrs: BTreeSet<&str> =
                                            accr.attrs.iter().map(|s| s.as_str()).collect();
                                        let allowed_classes: BTreeSet<&str> =
                                            accr.classes.iter().map(|s| s.as_str()).collect();

                                        if !create_attrs.is_subset(&allowed_attrs) {
                                            lsecurity_access!(
                                                audit,
                                                "create_attrs is not a subset of allowed"
                                            );
                                            lsecurity_access!(
                                                audit,
                                                "{:?} !⊆ {:?}",
                                                create_attrs,
                                                allowed_attrs
                                            );
                                            return false;
                                        }
                                        if !create_classes.is_subset(&allowed_classes) {
                                            lsecurity_access!(
                                                audit,
                                                "create_classes is not a subset of allowed"
                                            );
                                            lsecurity_access!(
                                                audit,
                                                "{:?} !⊆ {:?}",
                                                create_classes,
                                                allowed_classes
                                            );
                                            return false;
                                        }
                                        lsecurity_access!(audit, "passed");

                                        true
                                    } else {
                                        lsecurity_access!(
                                            audit,
                                            "entry {:?} DOES NOT match acs {:?}",
                                            e,
                                            accr
                                        );
                                        // Does not match, fail this rule.
                                        false
                                    }
                                }
                                Err(e) => {
                                    ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                    // Default to failing here.
                                    false
                                }
                            } // match
                        }
                    })
                }
                //      Find the set of related acps for this entry.
                //
                //      For each "created" entry.
                //          If the created entry is 100% allowed by this acp
                //          IE: all attrs to be created AND classes match classes
                //              allow
                //          if no acp allows, fail operation.
            });

            if r {
                lsecurity_access!(audit, "allowed ✅");
            } else {
                lsecurity_access!(audit, "denied ❌");
            }

            Ok(r)
        })
    }

    fn delete_allow_operation(
        &self,
        audit: &mut AuditScope,
        de: &DeleteEvent,
        entries: &[Entry<EntrySealed, EntryCommitted>],
    ) -> Result<bool, OperationError> {
        let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &de.event.origin {
            EventOrigin::Internal => {
                ltrace!(audit, "Internal operation, bypassing access check");
                // No need to check ACS
                return Ok(true);
            }
            EventOrigin::User(e) => &e,
        };
        lperf_segment!(audit, "access::delete_allow_operation", || {
            lsecurity_access!(audit, "Access check for event: {:?}", de);

            // Some useful references we'll use for the remainder of the operation
            let delete_state = self.get_delete();

            // Find the acps that relate to the caller.
            let related_acp: Vec<&AccessControlDelete> = delete_state
                .iter()
                .filter(|acs| {
                    let f_val = acs.acp.receiver.clone();
                    match f_val.resolve(&de.event, None) {
                        Ok(f_res) => rec_entry.entry_match_no_index(&f_res),
                        Err(e) => {
                            ladmin_error!(
                                audit,
                                "A internal filter was passed for resolution!?!? {:?}",
                                e
                            );
                            false
                        }
                    }
                })
                .collect();

            related_acp.iter().for_each(|racp| {
                lsecurity_access!(audit, "Related acs -> {:?}", racp.acp.name);
            });

            // For each entry
            let r = entries.iter().fold(true, |acc, e| {
                if !acc {
                    // Any false, denies the whole operation.
                    false
                } else {
                    related_acp.iter().fold(false, |r_acc, acd| {
                        if r_acc {
                            // If something allowed us to delete, skip doing silly work.
                            r_acc
                        } else {
                            let f_val = acd.acp.targetscope.clone();
                            match f_val.resolve(&de.event, None) {
                                Ok(f_res) => {
                                    if e.entry_match_no_index(&f_res) {
                                        lsecurity_access!(
                                            audit,
                                            "entry {:?} matches acs {:?}",
                                            e.get_uuid(),
                                            acd
                                        );
                                        // It matches, so we can delete this!
                                        lsecurity_access!(audit, "passed");
                                        true
                                    } else {
                                        lsecurity_access!(
                                            audit,
                                            "entry {:?} DOES NOT match acs {:?}",
                                            e.get_uuid(),
                                            acd
                                        );
                                        // Does not match, fail.
                                        false
                                    }
                                }
                                Err(e) => {
                                    ladmin_error!(
                                        audit,
                                        "A internal filter was passed for resolution!?!? {:?}",
                                        e
                                    );
                                    // Default to failing here.
                                    false
                                }
                            } // match
                        } // else
                    }) // fold related_acp
                } // if/else
            });
            if r {
                lsecurity_access!(audit, "allowed ✅");
            } else {
                lsecurity_access!(audit, "denied ❌");
            }
            Ok(r)
        })
    }
}

pub struct AccessControlsWriteTransaction<'a> {
    inner: CowCellWriteTxn<'a, AccessControlsInner>,
}

impl<'a> AccessControlsWriteTransaction<'a> {
    // We have a method to update each set, so that if an error
    // occurs we KNOW it's an error, rather than using errors as
    // part of the logic (IE try-parse-fail method).
    pub fn update_search(
        &mut self,
        mut acps: Vec<AccessControlSearch>,
    ) -> Result<(), OperationError> {
        // Clear the existing tree. We don't care that we are wiping it
        // because we have the transactions to protect us from errors
        // to allow rollbacks.
        /*
        let acps_search = &mut self.inner.deref_mut().acps_search;
        acps_search.clear();
        for acp in acps {
            let uuid = acp.acp.uuid;
            acps_search.insert(uuid, acp);
        }
        */
        std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_search);
        Ok(())
    }

    pub fn update_create(
        &mut self,
        mut acps: Vec<AccessControlCreate>,
    ) -> Result<(), OperationError> {
        std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_create);
        Ok(())
    }

    pub fn update_modify(
        &mut self,
        mut acps: Vec<AccessControlModify>,
    ) -> Result<(), OperationError> {
        std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_modify);
        Ok(())
    }

    pub fn update_delete(
        &mut self,
        mut acps: Vec<AccessControlDelete>,
    ) -> Result<(), OperationError> {
        std::mem::swap(&mut acps, &mut self.inner.deref_mut().acps_delete);
        Ok(())
    }

    pub fn commit(self) -> Result<(), OperationError> {
        self.inner.commit();

        Ok(())
    }
}

impl<'a> AccessControlsTransaction for AccessControlsWriteTransaction<'a> {
    fn get_search(&self) -> &Vec<AccessControlSearch> {
        &self.inner.acps_search
    }

    fn get_create(&self) -> &Vec<AccessControlCreate> {
        &self.inner.acps_create
    }

    fn get_modify(&self) -> &Vec<AccessControlModify> {
        &self.inner.acps_modify
    }

    fn get_delete(&self) -> &Vec<AccessControlDelete> {
        &self.inner.acps_delete
    }
}

// =========================================================================
// ACP operations (Should this actually be on the ACP's themself?
// =========================================================================

pub struct AccessControlsReadTransaction {
    inner: CowCellReadTxn<AccessControlsInner>,
}

impl AccessControlsTransaction for AccessControlsReadTransaction {
    fn get_search(&self) -> &Vec<AccessControlSearch> {
        &self.inner.acps_search
    }

    fn get_create(&self) -> &Vec<AccessControlCreate> {
        &self.inner.acps_create
    }

    fn get_modify(&self) -> &Vec<AccessControlModify> {
        &self.inner.acps_modify
    }

    fn get_delete(&self) -> &Vec<AccessControlDelete> {
        &self.inner.acps_delete
    }
}

// =========================================================================
// ACP transaction operations
// =========================================================================

impl AccessControls {
    pub fn new() -> Self {
        AccessControls {
            inner: CowCell::new(AccessControlsInner {
                acps_search: Vec::new(),
                acps_create: Vec::new(),
                acps_modify: Vec::new(),
                acps_delete: Vec::new(),
            }),
        }
    }

    pub fn read(&self) -> AccessControlsReadTransaction {
        AccessControlsReadTransaction {
            inner: self.inner.read(),
        }
    }

    pub fn write(&self) -> AccessControlsWriteTransaction {
        AccessControlsWriteTransaction {
            inner: self.inner.write(),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::access::{
        AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlProfile,
        AccessControlSearch, AccessControls, AccessControlsTransaction,
    };
    use crate::audit::AuditScope;
    use crate::entry::{Entry, EntryCommitted, EntryInit, EntryNew, EntryReduced};
    // use crate::server::QueryServerWriteTransaction;

    use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent};
    // use crate::filter::Filter;
    // use crate::proto_v1::Filter as ProtoFilter;
    use crate::constants::{JSON_ADMIN_V1, JSON_ANONYMOUS_V1, JSON_TESTPERSON1, JSON_TESTPERSON2};
    use crate::value::{PartialValue, Value};

    macro_rules! acp_from_entry_err {
        (
            $audit:expr,
            $qs:expr,
            $e:expr,
            $type:ty
        ) => {{
            let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str($e);
            let ev1 = unsafe { e1.into_sealed_committed() };

            let r1 = <$type>::try_from($audit, $qs, &ev1);
            assert!(r1.is_err());
        }};
    }

    macro_rules! acp_from_entry_ok {
        (
            $audit:expr,
            $qs:expr,
            $e:expr,
            $type:ty
        ) => {{
            let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str($e);
            let ev1 = unsafe { e1.into_sealed_committed() };

            let r1 = <$type>::try_from($audit, $qs, &ev1);
            assert!(r1.is_ok());
            r1.unwrap()
        }};
    }

    #[test]
    fn test_access_acp_parser() {
        run_test!(|qs: &QueryServer, audit: &mut AuditScope| {
            // Test parsing entries to acp. There so no point testing schema violations
            // because the schema system is well tested an robust. Instead we target
            // entry misconfigurations, such as missing classes required.

            // Generally, we are testing the *positive* cases here, because schema
            // really protects us *a lot* here, but it's nice to have defence and
            // layers of validation.

            let mut qs_write = qs.write(duration_from_epoch_now());

            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object"],
                        "name": ["acp_invalid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
                    }
                }"#,
                AccessControlProfile
            );

            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_invalid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
                    }
                }"#,
                AccessControlProfile
            );

            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_invalid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [""],
                        "acp_targetscope": [""]
                    }
                }"#,
                AccessControlProfile
            );

            // "\"Self\""
            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ]
                    }
                }"#,
                AccessControlProfile
            );
        })
    }

    #[test]
    fn test_access_acp_delete_parser() {
        run_test!(|qs: &QueryServer, audit: &mut AuditScope| {
            let mut qs_write = qs.write(duration_from_epoch_now());

            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ]
                    }
                }"#,
                AccessControlDelete
            );

            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_delete"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ]
                    }
                }"#,
                AccessControlDelete
            );
        })
    }

    #[test]
    fn test_access_acp_search_parser() {
        run_test!(|qs: &QueryServer, audit: &mut AuditScope| {
            // Test that parsing search access controls works.
            let mut qs_write = qs.write(duration_from_epoch_now());

            // Missing class acp
            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_search"],
                        "name": ["acp_invalid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_search_attr": ["name", "class"]
                    }
                }"#,
                AccessControlSearch
            );

            // Missing class acs
            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_invalid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_search_attr": ["name", "class"]
                    }
                }"#,
                AccessControlSearch
            );

            // Missing attr acp_search_attr
            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_search"],
                        "name": ["acp_invalid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ]
                    }
                }"#,
                AccessControlSearch
            );

            // All good!
            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_search"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_search_attr": ["name", "class"]
                    }
                }"#,
                AccessControlSearch
            );
        })
    }

    #[test]
    fn test_access_acp_modify_parser() {
        run_test!(|qs: &QueryServer, audit: &mut AuditScope| {
            // Test that parsing modify access controls works.
            let mut qs_write = qs.write(duration_from_epoch_now());

            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_modify_removedattr": ["name"],
                        "acp_modify_presentattr": ["name"],
                        "acp_modify_class": ["object"]
                    }
                }"#,
                AccessControlModify
            );

            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_modify"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ]
                    }
                }"#,
                AccessControlModify
            );

            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_modify"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_modify_removedattr": ["name"],
                        "acp_modify_presentattr": ["name"],
                        "acp_modify_class": ["object"]
                    }
                }"#,
                AccessControlModify
            );
        })
    }

    #[test]
    fn test_access_acp_create_parser() {
        run_test!(|qs: &QueryServer, audit: &mut AuditScope| {
            // Test that parsing create access controls works.
            let mut qs_write = qs.write(duration_from_epoch_now());

            acp_from_entry_err!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_create_class": ["object"],
                        "acp_create_attr": ["name"]
                    }
                }"#,
                AccessControlCreate
            );

            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_create"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ]
                    }
                }"#,
                AccessControlCreate
            );

            acp_from_entry_ok!(
                audit,
                &mut qs_write,
                r#"{
                    "attrs": {
                        "class": ["object", "access_control_profile", "access_control_create"],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_create_class": ["object"],
                        "acp_create_attr": ["name"]
                    }
                }"#,
                AccessControlCreate
            );
        })
    }

    #[test]
    fn test_access_acp_compound_parser() {
        run_test!(|qs: &QueryServer, audit: &mut AuditScope| {
            // Test that parsing compound access controls works. This means that
            // given a single &str, we can evaluate all types from a single record.
            // This is valid, and could exist, IE a rule to allow create, search and modify
            // over a single scope.
            let mut qs_write = qs.write(duration_from_epoch_now());

            let e: &str = r#"{
                    "attrs": {
                        "class": [
                            "object",
                            "access_control_profile",
                            "access_control_create",
                            "access_control_delete",
                            "access_control_modify",
                            "access_control_search"
                        ],
                        "name": ["acp_valid"],
                        "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
                        "acp_receiver": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_targetscope": [
                            "{\"eq\":[\"name\",\"a\"]}"
                        ],
                        "acp_search_attr": ["name"],
                        "acp_create_class": ["object"],
                        "acp_create_attr": ["name"],
                        "acp_modify_removedattr": ["name"],
                        "acp_modify_presentattr": ["name"],
                        "acp_modify_class": ["object"]
                    }
                }"#;

            acp_from_entry_ok!(audit, &mut qs_write, e, AccessControlCreate);
            acp_from_entry_ok!(audit, &mut qs_write, e, AccessControlDelete);
            acp_from_entry_ok!(audit, &mut qs_write, e, AccessControlModify);
            acp_from_entry_ok!(audit, &mut qs_write, e, AccessControlSearch);
        })
    }

    macro_rules! test_acp_search {
        (
            $se:expr,
            $controls:expr,
            $entries:expr,
            $expect:expr
        ) => {{
            let ac = AccessControls::new();
            let mut acw = ac.write();
            acw.update_search($controls).expect("Failed to update");
            let acw = acw;

            let mut audit = AuditScope::new("test_acp_search", uuid::Uuid::new_v4(), None);
            let res = acw
                .search_filter_entries(&mut audit, $se, $entries)
                .expect("op failed");
            audit.write_log();
            debug!("result --> {:?}", res);
            debug!("expect --> {:?}", $expect);
            // should be ok, and same as expect.
            assert!(res == $expect);
        }};
    }

    #[test]
    fn test_access_internal_search() {
        // Test that an internal search bypasses ACS
        let se = unsafe { SearchEvent::new_internal_invalid(filter!(f_pres("class"))) };

        let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
            r#"{
                "attrs": {
                    "class": ["object"],
                    "name": ["testperson1"],
                    "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
                }
                }"#,
        );
        let ev1 = unsafe { e1.into_sealed_committed() };

        let expect = vec![ev1.clone()];
        let entries = vec![ev1];

        // This acp basically is "allow access to stuff, but not this".
        test_acp_search!(
            &se,
            vec![unsafe {
                AccessControlSearch::from_raw(
                    "test_acp",
                    "d38640c4-0254-49f9-99b7-8ba7d0233f3d",
                    filter_valid!(f_pres("class")), // apply to all people
                    filter_valid!(f_pres("nomatchy")), // apply to none - ie no allowed results
                    "name",                         // allow to this attr, but we don't eval this.
                )
            }],
            entries,
            expect
        );
    }

    #[test]
    fn test_access_enforce_search() {
        // Test that entries from a search are reduced by acps
        let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
        let ev1 = unsafe { e1.into_sealed_committed() };

        let e2: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON2);
        let ev2 = unsafe { e2.into_sealed_committed() };

        let r_set = vec![ev1.clone(), ev2.clone()];

        let se_admin = unsafe {
            SearchEvent::new_impersonate_entry_ser(JSON_ADMIN_V1, filter_all!(f_pres("name")))
        };
        let ex_admin = vec![ev1.clone()];

        let se_anon = unsafe {
            SearchEvent::new_impersonate_entry_ser(JSON_ANONYMOUS_V1, filter_all!(f_pres("name")))
        };
        let ex_anon = vec![];

        let acp = unsafe {
            AccessControlSearch::from_raw(
                "test_acp",
                "d38640c4-0254-49f9-99b7-8ba7d0233f3d",
                // apply to admin only
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // Allow admin to read only testperson1
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // In that read, admin may only view the "name" attribute, or query on
                // the name attribute. Any other query (should be) rejected.
                "name",
            )
        };

        // Check the admin search event
        test_acp_search!(&se_admin, vec![acp.clone()], r_set.clone(), ex_admin);

        // Check the anonymous
        test_acp_search!(&se_anon, vec![acp], r_set, ex_anon);
    }

    macro_rules! test_acp_search_reduce {
        (
            $se:expr,
            $controls:expr,
            $entries:expr,
            $expect:expr
        ) => {{
            let ac = AccessControls::new();
            let mut acw = ac.write();
            acw.update_search($controls).expect("Failed to update");
            let acw = acw;

            let mut audit = AuditScope::new("test_acp_search_reduce", uuid::Uuid::new_v4(), None);
            // We still have to reduce the entries to be sure that we are good.
            let res = acw
                .search_filter_entries(&mut audit, $se, $entries)
                .expect("operation failed");
            // Now on the reduced entries, reduce the entries attrs.
            let reduced = acw
                .search_filter_entry_attributes(&mut audit, $se, res)
                .expect("operation failed");

            // Help the type checker for the expect set.
            let expect_set: Vec<Entry<EntryReduced, EntryCommitted>> = $expect
                .into_iter()
                .map(|e| unsafe { e.into_reduced() })
                .collect();

            audit.write_log();

            debug!("expect --> {:?}", expect_set);
            debug!("result --> {:?}", reduced);
            // should be ok, and same as expect.
            assert!(reduced == expect_set);
        }};
    }

    const JSON_TESTPERSON1_REDUCED: &'static str = r#"{
        "attrs": {
            "name": ["testperson1"]
        }
    }"#;

    #[test]
    fn test_access_enforce_search_attrs() {
        // Test that attributes are correctly limited.
        // In this case, we test that a user can only see "name" despite the
        // class and uuid being present.
        let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
        let ev1 = unsafe { e1.into_sealed_committed() };
        let r_set = vec![ev1.clone()];

        let ex1: Entry<EntryInit, EntryNew> =
            Entry::unsafe_from_entry_str(JSON_TESTPERSON1_REDUCED);
        let exv1 = unsafe { ex1.into_sealed_committed() };
        let ex_anon = vec![exv1.clone()];

        let se_anon = unsafe {
            SearchEvent::new_impersonate_entry_ser(
                JSON_ANONYMOUS_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
            )
        };

        let acp = unsafe {
            AccessControlSearch::from_raw(
                "test_acp",
                "d38640c4-0254-49f9-99b7-8ba7d0233f3d",
                // apply to anonymous only
                filter_valid!(f_eq("name", PartialValue::new_iname("anonymous"))),
                // Allow anonymous to read only testperson1
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // In that read, admin may only view the "name" attribute, or query on
                // the name attribute. Any other query (should be) rejected.
                "name",
            )
        };

        // Finally test it!
        test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
    }

    #[test]
    fn test_access_enforce_search_attrs_req() {
        // Test that attributes are correctly limited by the request.
        // In this case, we test that a user can only see "name" despite the
        // class and uuid being present.
        let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
        let ev1 = unsafe { e1.into_sealed_committed() };
        let r_set = vec![ev1.clone()];

        let ex1: Entry<EntryInit, EntryNew> =
            Entry::unsafe_from_entry_str(JSON_TESTPERSON1_REDUCED);
        let exv1 = unsafe { ex1.into_sealed_committed() };
        let ex_anon = vec![exv1.clone()];

        let mut se_anon = unsafe {
            SearchEvent::new_impersonate_entry_ser(
                JSON_ANONYMOUS_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
            )
        };
        // the requested attrs here.
        se_anon.attrs = Some(btreeset!["name".to_string()]);

        let acp = unsafe {
            AccessControlSearch::from_raw(
                "test_acp",
                "d38640c4-0254-49f9-99b7-8ba7d0233f3d",
                // apply to anonymous only
                filter_valid!(f_eq("name", PartialValue::new_iname("anonymous"))),
                // Allow anonymous to read only testperson1
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // In that read, admin may only view the "name" attribute, or query on
                // the name attribute. Any other query (should be) rejected.
                "name uuid",
            )
        };

        // Finally test it!
        test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
    }

    macro_rules! test_acp_modify {
        (
            $me:expr,
            $controls:expr,
            $entries:expr,
            $expect:expr
        ) => {{
            let ac = AccessControls::new();
            let mut acw = ac.write();
            acw.update_modify($controls).expect("Failed to update");
            let acw = acw;

            let mut audit = AuditScope::new("test_acp_modify", uuid::Uuid::new_v4(), None);
            let res = acw
                .modify_allow_operation(&mut audit, $me, $entries)
                .expect("op failed");

            audit.write_log();
            debug!("result --> {:?}", res);
            debug!("expect --> {:?}", $expect);
            // should be ok, and same as expect.
            assert!(res == $expect);
        }};
    }

    #[test]
    fn test_access_enforce_modify() {
        let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
        let ev1 = unsafe { e1.into_sealed_committed() };
        let r_set = vec![ev1.clone()];

        // Name present
        let me_pres = unsafe {
            ModifyEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
                modlist!([m_pres("name", &Value::new_iname_s("value"))]),
            )
        };
        // Name rem
        let me_rem = unsafe {
            ModifyEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
                modlist!([m_remove("name", &PartialValue::new_iname("value"))]),
            )
        };
        // Name purge
        let me_purge = unsafe {
            ModifyEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
                modlist!([m_purge("name")]),
            )
        };

        // Class account pres
        let me_pres_class = unsafe {
            ModifyEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
                modlist!([m_pres("class", &Value::new_class("account"))]),
            )
        };
        // Class account rem
        let me_rem_class = unsafe {
            ModifyEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
                modlist!([m_remove("class", &PartialValue::new_class("account"))]),
            )
        };
        // Class purge
        let me_purge_class = unsafe {
            ModifyEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
                modlist!([m_purge("class")]),
            )
        };

        // Allow name and class, class is account
        let acp_allow = unsafe {
            AccessControlModify::from_raw(
                "test_modify_allow",
                "87bfe9b8-7600-431e-a492-1dde64bbc455",
                // Apply to admin
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // To modify testperson
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // Allow pres name and class
                "name class",
                // Allow rem name and class
                "name class",
                // And the class allowed is account
                "account",
            )
        };
        // Allow member, class is group. IE not account
        let acp_deny = unsafe {
            AccessControlModify::from_raw(
                "test_modify_deny",
                "87bfe9b8-7600-431e-a492-1dde64bbc456",
                // Apply to admin
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // To modify testperson
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // Allow pres name and class
                "member class",
                // Allow rem name and class
                "member class",
                // And the class allowed is account
                "group",
            )
        };
        // Does not have a pres or rem class in attrs
        let acp_no_class = unsafe {
            AccessControlModify::from_raw(
                "test_modify_no_class",
                "87bfe9b8-7600-431e-a492-1dde64bbc457",
                // Apply to admin
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // To modify testperson
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // Allow pres name and class
                "name class",
                // Allow rem name and class
                "name class",
                // And the class allowed is NOT an account ...
                "group",
            )
        };

        // Test allowed pres
        test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r_set, true);
        // test allowed rem
        test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r_set, true);
        // test allowed purge
        test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r_set, true);

        // Test rejected pres
        test_acp_modify!(&me_pres, vec![acp_deny.clone()], &r_set, false);
        // Test rejected rem
        test_acp_modify!(&me_rem, vec![acp_deny.clone()], &r_set, false);
        // Test rejected purge
        test_acp_modify!(&me_purge, vec![acp_deny.clone()], &r_set, false);

        // test allowed pres class
        test_acp_modify!(&me_pres_class, vec![acp_allow.clone()], &r_set, true);
        // test allowed rem class
        test_acp_modify!(&me_rem_class, vec![acp_allow.clone()], &r_set, true);
        // test reject purge-class even if class present in allowed remattrs
        test_acp_modify!(&me_purge_class, vec![acp_allow.clone()], &r_set, false);

        // Test reject pres class, but class not in classes
        test_acp_modify!(&me_pres_class, vec![acp_no_class.clone()], &r_set, false);
        // Test reject pres class, class in classes but not in pres attrs
        test_acp_modify!(&me_pres_class, vec![acp_deny.clone()], &r_set, false);
        // test reject rem class, but class not in classes
        test_acp_modify!(&me_rem_class, vec![acp_no_class.clone()], &r_set, false);
        // test reject rem class, class in classes but not in pres attrs
        test_acp_modify!(&me_rem_class, vec![acp_deny.clone()], &r_set, false);
    }

    macro_rules! test_acp_create {
        (
            $ce:expr,
            $controls:expr,
            $entries:expr,
            $expect:expr
        ) => {{
            let ac = AccessControls::new();
            let mut acw = ac.write();
            acw.update_create($controls).expect("Failed to update");
            let acw = acw;

            let mut audit = AuditScope::new("test_acp_create", uuid::Uuid::new_v4(), None);
            let res = acw
                .create_allow_operation(&mut audit, $ce, $entries)
                .expect("op failed");

            audit.write_log();
            debug!("result --> {:?}", res);
            debug!("expect --> {:?}", $expect);
            // should be ok, and same as expect.
            assert!(res == $expect);
        }};
    }

    const JSON_TEST_CREATE_AC1: &'static str = r#"{
        "attrs": {
            "class": ["account"],
            "name": ["testperson1"],
            "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
        }
    }"#;

    const JSON_TEST_CREATE_AC2: &'static str = r#"{
        "attrs": {
            "class": ["account"],
            "name": ["testperson1"],
            "notallowed": ["not allowed!"],
            "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
        }
    }"#;

    const JSON_TEST_CREATE_AC3: &'static str = r#"{
        "attrs": {
            "class": ["account", "notallowed"],
            "name": ["testperson1"],
            "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
        }
    }"#;

    const JSON_TEST_CREATE_AC4: &'static str = r#"{
        "attrs": {
            "class": ["account", "group"],
            "name": ["testperson1"],
            "uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
        }
    }"#;

    #[test]
    fn test_access_enforce_create() {
        let ev1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC1);
        let r1_set = vec![ev1.clone()];

        let ev2: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC2);
        let r2_set = vec![ev2.clone()];

        let ev3: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC3);
        let r3_set = vec![ev3.clone()];

        let ev4: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC4);
        let r4_set = vec![ev4.clone()];

        // In this case, we can make the create event with an empty entry
        // set because we only reference the entries in r_set in the test.
        //
        // In the realy server code, the entry set is derived from and checked
        // against the create event, so we have some level of trust in it.
        let ce_admin = unsafe { CreateEvent::new_impersonate_entry_ser(JSON_ADMIN_V1, vec![]) };

        let acp = unsafe {
            AccessControlCreate::from_raw(
                "test_create",
                "87bfe9b8-7600-431e-a492-1dde64bbc453",
                // Apply to admin
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // To create matching filter testperson
                // Can this be empty?
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // classes
                "account",
                // attrs
                "class name uuid",
            )
        };

        let acp2 = unsafe {
            AccessControlCreate::from_raw(
                "test_create_2",
                "87bfe9b8-7600-431e-a492-1dde64bbc454",
                // Apply to admin
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // To create matching filter testperson
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
                // classes
                "group",
                // attrs
                "class name uuid",
            )
        };

        // Test allowed to create
        test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
        // Test reject create (not allowed attr)
        test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
        // Test reject create (not allowed class)
        test_acp_create!(&ce_admin, vec![acp.clone()], &r3_set, false);
        // Test reject create (hybrid u + g entry w_ u & g create allow)
        test_acp_create!(&ce_admin, vec![acp, acp2], &r4_set, false);
    }

    macro_rules! test_acp_delete {
        (
            $de:expr,
            $controls:expr,
            $entries:expr,
            $expect:expr
        ) => {{
            let ac = AccessControls::new();
            let mut acw = ac.write();
            acw.update_delete($controls).expect("Failed to update");
            let acw = acw;

            let mut audit = AuditScope::new("test_acp_delete", uuid::Uuid::new_v4(), None);
            let res = acw
                .delete_allow_operation(&mut audit, $de, $entries)
                .expect("op failed");

            audit.write_log();
            debug!("result --> {:?}", res);
            debug!("expect --> {:?}", $expect);
            // should be ok, and same as expect.
            assert!(res == $expect);
        }};
    }

    #[test]
    fn test_access_enforce_delete() {
        let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
        let ev1 = unsafe { e1.into_sealed_committed() };
        let r_set = vec![ev1.clone()];

        let de_admin = unsafe {
            DeleteEvent::new_impersonate_entry_ser(
                JSON_ADMIN_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
            )
        };

        let de_anon = unsafe {
            DeleteEvent::new_impersonate_entry_ser(
                JSON_ANONYMOUS_V1,
                filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
            )
        };

        let acp = unsafe {
            AccessControlDelete::from_raw(
                "test_delete",
                "87bfe9b8-7600-431e-a492-1dde64bbc453",
                // Apply to admin
                filter_valid!(f_eq("name", PartialValue::new_iname("admin"))),
                // To delete testperson
                filter_valid!(f_eq("name", PartialValue::new_iname("testperson1"))),
            )
        };

        // Test allowed to delete
        test_acp_delete!(&de_admin, vec![acp.clone()], &r_set, true);
        // Test reject delete
        test_acp_delete!(&de_anon, vec![acp], &r_set, false);
    }
}