use hashbrown::HashMap;
use std::cell::Cell;
use std::collections::BTreeSet;
use std::ops::DerefMut;
use std::sync::Arc;
use concread::arcache::ARCacheBuilder;
use concread::cowcell::*;
use uuid::Uuid;
use crate::entry::{Entry, EntryCommitted, EntryInit, EntryNew, EntryReduced};
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent};
use crate::filter::{Filter, FilterValid, ResolveFilterCache, ResolveFilterCacheReadTxn};
use crate::modify::Modify;
use crate::prelude::*;
use self::profiles::{
AccessControlCreate, AccessControlCreateResolved, AccessControlDelete,
AccessControlDeleteResolved, AccessControlModify, AccessControlModifyResolved,
AccessControlReceiver, AccessControlReceiverCondition, AccessControlSearch,
AccessControlSearchResolved, AccessControlTarget, AccessControlTargetCondition,
};
use self::create::{apply_create_access, CreateResult};
use self::delete::{apply_delete_access, DeleteResult};
use self::modify::{apply_modify_access, ModifyResult};
use self::search::{apply_search_access, SearchResult};
const ACP_RESOLVE_FILTER_CACHE_MAX: usize = 256;
const ACP_RESOLVE_FILTER_CACHE_LOCAL: usize = 0;
mod create;
mod delete;
mod modify;
pub mod profiles;
mod search;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Access {
Grant,
Denied,
Allow(BTreeSet<Attribute>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccessClass {
Grant,
Denied,
Allow(BTreeSet<AttrString>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccessEffectivePermission {
pub target: Uuid,
pub delete: bool,
pub search: Access,
pub modify_pres: Access,
pub modify_rem: Access,
pub modify_class: AccessClass,
}
pub enum AccessResult {
Denied,
Grant,
Ignore,
Constrain(BTreeSet<Attribute>),
Allow(BTreeSet<Attribute>),
}
#[allow(dead_code)]
pub enum AccessResultClass<'a> {
Denied,
Grant,
Ignore,
Constrain(BTreeSet<&'a str>),
Allow(BTreeSet<&'a str>),
}
#[derive(Clone)]
struct AccessControlsInner {
acps_search: Vec<AccessControlSearch>,
acps_create: Vec<AccessControlCreate>,
acps_modify: Vec<AccessControlModify>,
acps_delete: Vec<AccessControlDelete>,
sync_agreements: HashMap<Uuid, BTreeSet<Attribute>>,
}
pub struct AccessControls {
inner: CowCell<AccessControlsInner>,
acp_resolve_filter_cache: ResolveFilterCache,
}
fn resolve_access_conditions(
ident: &Identity,
ident_memberof: Option<&BTreeSet<Uuid>>,
receiver: &AccessControlReceiver,
target: &AccessControlTarget,
acp_resolve_filter_cache: &mut ResolveFilterCacheReadTxn<'_>,
) -> Option<(AccessControlReceiverCondition, AccessControlTargetCondition)> {
let receiver_condition = match receiver {
AccessControlReceiver::Group(groups) => {
let group_check = ident_memberof
.map(|imo| {
trace!(?imo, ?groups);
imo.intersection(groups).next().is_some()
})
.unwrap_or_default();
if group_check {
AccessControlReceiverCondition::GroupChecked
} else {
return None;
}
}
AccessControlReceiver::EntryManager => AccessControlReceiverCondition::EntryManager,
AccessControlReceiver::None => return None,
};
let target_condition = match &target {
AccessControlTarget::Scope(filter) => filter
.resolve(ident, None, Some(acp_resolve_filter_cache))
.map_err(|e| {
admin_error!(?e, "A internal filter/event was passed for resolution!?!?");
e
})
.ok()
.map(AccessControlTargetCondition::Scope)?,
AccessControlTarget::None => return None,
};
Some((receiver_condition, target_condition))
}
pub trait AccessControlsTransaction<'a> {
fn get_search(&self) -> &Vec<AccessControlSearch>;
fn get_create(&self) -> &Vec<AccessControlCreate>;
fn get_modify(&self) -> &Vec<AccessControlModify>;
fn get_delete(&self) -> &Vec<AccessControlDelete>;
fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>>;
#[allow(clippy::mut_from_ref)]
fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a>;
#[instrument(level = "trace", name = "access::search_related_acp", skip_all)]
fn search_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlSearchResolved<'b>> {
let search_state = self.get_search();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let ident_memberof = ident.get_memberof();
let related_acp: Vec<AccessControlSearchResolved<'b>> = search_state
.iter()
.filter_map(|acs| {
let (receiver_condition, target_condition) = resolve_access_conditions(
ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlSearchResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
related_acp
}
#[instrument(level = "debug", name = "access::filter_entries", skip_all)]
fn filter_entries(
&self,
ident: &Identity,
filter_orig: &Filter<FilterValid>,
entries: Vec<Arc<EntrySealedCommitted>>,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
let requested_attrs: BTreeSet<Attribute> = filter_orig.get_attr_set();
let related_acp = self.search_related_acp(ident);
let entries_is_empty = entries.is_empty();
let allowed_entries: Vec<_> = entries
.into_iter()
.filter(|e| {
match apply_search_access(ident, related_acp.as_slice(), e) {
SearchResult::Denied => false,
SearchResult::Grant => true,
SearchResult::Allow(allowed_attrs) => {
let decision = requested_attrs.is_subset(&allowed_attrs);
security_debug!(
?decision,
allowed = ?allowed_attrs,
requested = ?requested_attrs,
"search attribute decision",
);
decision
}
}
})
.collect();
if allowed_entries.is_empty() {
if !entries_is_empty {
security_access!("denied ❌ - no entries were released");
}
} else {
debug!("allowed search of {} entries ✅", allowed_entries.len());
}
Ok(allowed_entries)
}
#[inline(always)]
fn search_filter_entries(
&self,
se: &SearchEvent,
entries: Vec<Arc<EntrySealedCommitted>>,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
self.filter_entries(&se.ident, &se.filter_orig, entries)
}
#[instrument(
level = "debug",
name = "access::search_filter_entry_attributes",
skip_all
)]
fn search_filter_entry_attributes(
&self,
se: &SearchEvent,
entries: Vec<Arc<EntrySealedCommitted>>,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
let related_acp = self.search_related_acp(&se.ident);
let related_acp: Vec<_> = if let Some(r_attrs) = se.attrs.as_ref() {
related_acp
.into_iter()
.filter(|acs| !acs.acp.attrs.is_disjoint(r_attrs))
.collect()
} else {
related_acp
};
let entries_is_empty = entries.is_empty();
let allowed_entries: Vec<_> = entries
.into_iter()
.filter_map(|e| {
match apply_search_access(&se.ident, related_acp.as_slice(), &e) {
SearchResult::Denied | SearchResult::Grant => {
None
}
SearchResult::Allow(allowed_attrs) => {
debug!(
requested = ?se.attrs,
allowed = ?allowed_attrs,
"reduction",
);
let reduced_attrs = if let Some(requested) = se.attrs.as_ref() {
requested & &allowed_attrs
} else {
allowed_attrs
};
if reduced_attrs.is_empty() {
None
} else {
Some(e.reduce_attributes(&reduced_attrs))
}
}
}
})
.collect();
if allowed_entries.is_empty() {
if !entries_is_empty {
security_access!("reduced to empty set on all entries ❌");
}
} else {
debug!(
"attribute set reduced on {} entries ✅",
allowed_entries.len()
);
}
Ok(allowed_entries)
}
#[instrument(level = "trace", name = "access::modify_related_acp", skip_all)]
fn modify_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlModifyResolved<'b>> {
let modify_state = self.get_modify();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let ident_memberof = ident.get_memberof();
let related_acp: Vec<_> = modify_state
.iter()
.filter_map(|acs| {
trace!(acs_name = ?acs.acp.name);
let (receiver_condition, target_condition) = resolve_access_conditions(
ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlModifyResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
related_acp
}
#[instrument(level = "debug", name = "access::modify_allow_operation", skip_all)]
fn modify_allow_operation(
&self,
me: &ModifyEvent,
entries: &[Arc<EntrySealedCommitted>],
) -> Result<bool, OperationError> {
let disallow = me
.modlist
.iter()
.any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref()));
if disallow {
security_access!("Disallowing purge {} in modification", Attribute::Class);
return Ok(false);
}
let related_acp: Vec<_> = self.modify_related_acp(&me.ident);
let requested_pres: BTreeSet<Attribute> = me
.modlist
.iter()
.filter_map(|m| match m {
Modify::Present(a, _) => Some(a.clone()),
_ => None,
})
.collect();
let requested_rem: BTreeSet<Attribute> = me
.modlist
.iter()
.filter_map(|m| match m {
Modify::Removed(a, _) => Some(a.clone()),
Modify::Purged(a) => Some(a.clone()),
_ => None,
})
.collect();
let requested_classes: BTreeSet<&str> = me
.modlist
.iter()
.filter_map(|m| match m {
Modify::Present(a, v) => {
if a == Attribute::Class.as_ref() {
v.to_str()
} else {
None
}
}
Modify::Removed(a, v) => {
if a == Attribute::Class.as_ref() {
v.to_str()
} else {
None
}
}
_ => None,
})
.collect();
debug!(?requested_pres, "Requested present set");
debug!(?requested_rem, "Requested remove set");
debug!(?requested_classes, "Requested class set");
let sync_agmts = self.get_sync_agreements();
let r = entries.iter().all(|e| {
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
ModifyResult::Denied => false,
ModifyResult::Grant => true,
ModifyResult::Allow { pres, rem, cls } => {
if !requested_pres.is_subset(&pres) {
security_error!("requested_pres is not a subset of allowed");
security_error!(
"requested_pres: {:?} !⊆ allowed: {:?}",
requested_pres,
pres
);
false
} else if !requested_rem.is_subset(&rem) {
security_error!("requested_rem is not a subset of allowed");
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
false
} else if !requested_classes.is_subset(&cls) {
security_error!("requested_classes is not a subset of allowed");
security_error!(
"requested_classes: {:?} !⊆ allowed: {:?}",
requested_classes,
cls
);
false
} else {
debug!("passed pres, rem, classes check.");
true
} }
}
});
if r {
debug!("allowed modify of {} entries ✅", entries.len());
} else {
security_access!("denied ❌ - modify may not proceed");
}
Ok(r)
}
#[instrument(
level = "debug",
name = "access::batch_modify_allow_operation",
skip_all
)]
fn batch_modify_allow_operation(
&self,
me: &BatchModifyEvent,
entries: &[Arc<EntrySealedCommitted>],
) -> Result<bool, OperationError> {
let related_acp = self.modify_related_acp(&me.ident);
let r = entries.iter().all(|e| {
let Some(modlist) = me.modset.get(&e.get_uuid()) else {
security_access!(
"modlist not present for {}, failing operation.",
e.get_uuid()
);
return false;
};
let disallow = modlist
.iter()
.any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref()));
if disallow {
security_access!("Disallowing purge in modification");
return false;
}
let requested_pres: BTreeSet<Attribute> = modlist
.iter()
.filter_map(|m| match m {
Modify::Present(a, _) => Some(a.clone()),
_ => None,
})
.collect();
let requested_rem: BTreeSet<Attribute> = modlist
.iter()
.filter_map(|m| match m {
Modify::Removed(a, _) => Some(a.clone()),
Modify::Purged(a) => Some(a.clone()),
_ => None,
})
.collect();
let requested_classes: BTreeSet<&str> = modlist
.iter()
.filter_map(|m| match m {
Modify::Present(a, v) => {
if a == Attribute::Class.as_ref() {
v.to_str()
} else {
None
}
}
Modify::Removed(a, v) => {
if a == Attribute::Class.as_ref() {
v.to_str()
} else {
None
}
}
_ => None,
})
.collect();
debug!(?requested_pres, "Requested present set");
debug!(?requested_rem, "Requested remove set");
debug!(?requested_classes, "Requested class set");
let sync_agmts = self.get_sync_agreements();
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
ModifyResult::Denied => false,
ModifyResult::Grant => true,
ModifyResult::Allow { pres, rem, cls } => {
if !requested_pres.is_subset(&pres) {
security_error!("requested_pres is not a subset of allowed");
security_error!(
"requested_pres: {:?} !⊆ allowed: {:?}",
requested_pres,
pres
);
false
} else if !requested_rem.is_subset(&rem) {
security_error!("requested_rem is not a subset of allowed");
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
false
} else if !requested_classes.is_subset(&cls) {
security_error!("requested_classes is not a subset of allowed");
security_error!(
"requested_classes: {:?} !⊆ allowed: {:?}",
requested_classes,
cls
);
false
} else {
security_access!("passed pres, rem, classes check.");
true
} }
}
});
if r {
debug!("allowed modify of {} entries ✅", entries.len());
} else {
security_access!("denied ❌ - modifications may not proceed");
}
Ok(r)
}
#[instrument(level = "debug", name = "access::create_allow_operation", skip_all)]
fn create_allow_operation(
&self,
ce: &CreateEvent,
entries: &[Entry<EntryInit, EntryNew>],
) -> Result<bool, OperationError> {
let create_state = self.get_create();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let ident_memberof = ce.ident.get_memberof();
let related_acp: Vec<_> = create_state
.iter()
.filter_map(|acs| {
let (receiver_condition, target_condition) = resolve_access_conditions(
&ce.ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlCreateResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
let r = entries.iter().all(|e| {
match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
CreateResult::Denied => false,
CreateResult::Grant => true,
}
});
if r {
debug!("allowed create of {} entries ✅", entries.len());
} else {
security_access!("denied ❌ - create may not proceed");
}
Ok(r)
}
#[instrument(level = "trace", name = "access::delete_related_acp", skip_all)]
fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
let delete_state = self.get_delete();
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
let ident_memberof = ident.get_memberof();
let related_acp: Vec<_> = delete_state
.iter()
.filter_map(|acs| {
let (receiver_condition, target_condition) = resolve_access_conditions(
ident,
ident_memberof,
&acs.acp.receiver,
&acs.acp.target,
acp_resolve_filter_cache,
)?;
Some(AccessControlDeleteResolved {
acp: acs,
receiver_condition,
target_condition,
})
})
.collect();
related_acp
}
#[instrument(level = "debug", name = "access::delete_allow_operation", skip_all)]
fn delete_allow_operation(
&self,
de: &DeleteEvent,
entries: &[Arc<EntrySealedCommitted>],
) -> Result<bool, OperationError> {
let related_acp = self.delete_related_acp(&de.ident);
let r = entries.iter().all(|e| {
match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
DeleteResult::Denied => false,
DeleteResult::Grant => true,
}
});
if r {
debug!("allowed delete of {} entries ✅", entries.len());
} else {
security_access!("denied ❌ - delete may not proceed");
}
Ok(r)
}
#[instrument(level = "debug", name = "access::effective_permission_check", skip_all)]
fn effective_permission_check(
&self,
ident: &Identity,
attrs: Option<BTreeSet<Attribute>>,
entries: &[Arc<EntrySealedCommitted>],
) -> Result<Vec<AccessEffectivePermission>, OperationError> {
match &ident.origin {
IdentType::Internal => {
security_critical!("IMPOSSIBLE STATE: Internal search in external interface?! Returning empty for safety.");
return Err(OperationError::InvalidState);
}
IdentType::Synch(_) => {
security_critical!("Blocking sync check");
return Err(OperationError::InvalidState);
}
IdentType::User(_) => {}
};
trace!(ident = %ident, "Effective permission check");
let search_related_acp = self.search_related_acp(ident);
let search_related_acp = if let Some(r_attrs) = attrs.as_ref() {
search_related_acp
.into_iter()
.filter(|acs| !acs.acp.attrs.is_disjoint(r_attrs))
.collect()
} else {
search_related_acp
};
let modify_related_acp = self.modify_related_acp(ident);
let delete_related_acp = self.delete_related_acp(ident);
let sync_agmts = self.get_sync_agreements();
let effective_permissions: Vec<_> = entries
.iter()
.map(|e| {
let search_effective =
match apply_search_access(ident, search_related_acp.as_slice(), e) {
SearchResult::Denied => Access::Denied,
SearchResult::Grant => Access::Grant,
SearchResult::Allow(allowed_attrs) => {
Access::Allow(allowed_attrs.into_iter().collect())
}
};
let (modify_pres, modify_rem, modify_class) = match apply_modify_access(
ident,
modify_related_acp.as_slice(),
sync_agmts,
e,
) {
ModifyResult::Denied => (Access::Denied, Access::Denied, AccessClass::Denied),
ModifyResult::Grant => (Access::Grant, Access::Grant, AccessClass::Grant),
ModifyResult::Allow { pres, rem, cls } => (
Access::Allow(pres.into_iter().collect()),
Access::Allow(rem.into_iter().collect()),
AccessClass::Allow(cls.into_iter().map(|s| s.into()).collect()),
),
};
let delete_status = apply_delete_access(ident, delete_related_acp.as_slice(), e);
let delete = match delete_status {
DeleteResult::Denied => false,
DeleteResult::Grant => true,
};
AccessEffectivePermission {
target: e.get_uuid(),
delete,
search: search_effective,
modify_pres,
modify_rem,
modify_class,
}
})
.collect();
effective_permissions.iter().for_each(|ep| {
trace!(?ep);
});
Ok(effective_permissions)
}
}
pub struct AccessControlsWriteTransaction<'a> {
inner: CowCellWriteTxn<'a, AccessControlsInner>,
acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
}
impl<'a> AccessControlsWriteTransaction<'a> {
pub fn update_search(
&mut self,
mut acps: Vec<AccessControlSearch>,
) -> Result<(), OperationError> {
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 update_sync_agreements(
&mut self,
mut sync_agreements: HashMap<Uuid, BTreeSet<Attribute>>,
) {
std::mem::swap(
&mut sync_agreements,
&mut self.inner.deref_mut().sync_agreements,
);
}
pub fn commit(self) -> Result<(), OperationError> {
self.inner.commit();
Ok(())
}
}
impl<'a> AccessControlsTransaction<'a> 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
}
fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
&self.inner.sync_agreements
}
fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
unsafe {
let mptr = self.acp_resolve_filter_cache.as_ptr();
&mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
}
}
}
pub struct AccessControlsReadTransaction<'a> {
inner: CowCellReadTxn<AccessControlsInner>,
acp_resolve_filter_cache: Cell<ResolveFilterCacheReadTxn<'a>>,
}
unsafe impl<'a> Sync for AccessControlsReadTransaction<'a> {}
unsafe impl<'a> Send for AccessControlsReadTransaction<'a> {}
impl<'a> AccessControlsTransaction<'a> for AccessControlsReadTransaction<'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
}
fn get_sync_agreements(&self) -> &HashMap<Uuid, BTreeSet<Attribute>> {
&self.inner.sync_agreements
}
fn get_acp_resolve_filter_cache(&self) -> &mut ResolveFilterCacheReadTxn<'a> {
unsafe {
let mptr = self.acp_resolve_filter_cache.as_ptr();
&mut (*mptr) as &mut ResolveFilterCacheReadTxn<'a>
}
}
}
impl Default for AccessControls {
#![allow(clippy::expect_used)]
fn default() -> Self {
AccessControls {
inner: CowCell::new(AccessControlsInner {
acps_search: Vec::with_capacity(0),
acps_create: Vec::with_capacity(0),
acps_modify: Vec::with_capacity(0),
acps_delete: Vec::with_capacity(0),
sync_agreements: HashMap::default(),
}),
acp_resolve_filter_cache: ARCacheBuilder::new()
.set_size(ACP_RESOLVE_FILTER_CACHE_MAX, ACP_RESOLVE_FILTER_CACHE_LOCAL)
.set_reader_quiesce(true)
.build()
.expect("Failed to construct acp_resolve_filter_cache"),
}
}
}
impl AccessControls {
pub fn try_quiesce(&self) {
self.acp_resolve_filter_cache.try_quiesce();
}
pub fn read(&self) -> AccessControlsReadTransaction {
AccessControlsReadTransaction {
inner: self.inner.read(),
acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
}
}
pub fn write(&self) -> AccessControlsWriteTransaction {
AccessControlsWriteTransaction {
inner: self.inner.write(),
acp_resolve_filter_cache: Cell::new(self.acp_resolve_filter_cache.read()),
}
}
}
#[cfg(test)]
mod tests {
use hashbrown::HashMap;
use std::collections::BTreeSet;
use std::sync::Arc;
use uuid::uuid;
use super::{
profiles::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlProfile,
AccessControlSearch, AccessControlTarget,
},
Access, AccessClass, AccessControls, AccessControlsTransaction, AccessEffectivePermission,
};
use crate::prelude::*;
const UUID_TEST_ACCOUNT_1: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
const UUID_TEST_ACCOUNT_2: Uuid = uuid::uuid!("cec0852a-abdf-4ea6-9dae-d3157cb33d3a");
const UUID_TEST_GROUP_1: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
const UUID_TEST_GROUP_2: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
lazy_static! {
pub static ref E_TEST_ACCOUNT_1: Arc<EntrySealedCommitted> = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("test_account_1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed()
);
pub static ref E_TEST_ACCOUNT_2: Arc<EntrySealedCommitted> = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("test_account_1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_2)),
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_2))
)
.into_sealed_committed()
);
}
macro_rules! acp_from_entry_err {
(
$qs:expr,
$e:expr,
$type:ty
) => {{
let ev1 = $e.into_sealed_committed();
let r1 = <$type>::try_from($qs, &ev1);
error!(?r1);
assert!(r1.is_err());
}};
}
macro_rules! acp_from_entry_ok {
(
$qs:expr,
$e:expr,
$type:ty
) => {{
let ev1 = $e.into_sealed_committed();
let r1 = <$type>::try_from($qs, &ev1);
assert!(r1.is_ok());
r1.unwrap()
}};
}
#[qs_test]
async fn test_access_acp_parser(qs: &QueryServer) {
let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("acp_invalid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
)
),
AccessControlProfile
);
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlReceiverGroup.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlTargetScope.to_value()
),
(Attribute::Name, Value::new_iname("acp_invalid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
)
),
AccessControlProfile
);
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlReceiverGroup.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlTargetScope.to_value()
),
(Attribute::Name, Value::new_iname("acp_invalid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::AcpReceiverGroup, Value::Bool(true)),
(Attribute::AcpTargetScope, Value::Bool(true))
),
AccessControlProfile
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlReceiverGroup.to_value()
),
(
Attribute::Class,
EntryClass::AccessControlTargetScope.to_value()
),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlProfile
);
}
#[qs_test]
async fn test_access_acp_delete_parser(qs: &QueryServer) {
let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlDelete
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlDelete
);
}
#[qs_test]
async fn test_access_acp_search_parser(qs: &QueryServer) {
let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
(Attribute::AcpSearchAttr, Value::new_iutf8("class"))
),
AccessControlSearch
);
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
(Attribute::AcpSearchAttr, Value::new_iutf8("class"))
),
AccessControlSearch
);
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlSearch
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
(Attribute::AcpSearchAttr, Value::new_iutf8("class"))
),
AccessControlSearch
);
}
#[qs_test]
async fn test_access_acp_modify_parser(qs: &QueryServer) {
let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Name, Value::new_iname("acp_invalid")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlModify
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlModify
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(
Attribute::AcpModifyRemovedAttr,
Value::from(Attribute::Name)
),
(
Attribute::AcpModifyPresentAttr,
Value::from(Attribute::Name)
),
(Attribute::AcpModifyClass, EntryClass::Object.to_value())
),
AccessControlModify
);
}
#[qs_test]
async fn test_access_acp_create_parser(qs: &QueryServer) {
let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
acp_from_entry_err!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Name, Value::new_iname("acp_invalid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
(Attribute::AcpCreateClass, EntryClass::Object.to_value())
),
AccessControlCreate
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
)
),
AccessControlCreate
);
acp_from_entry_ok!(
&mut qs_write,
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
(Attribute::AcpCreateClass, EntryClass::Object.to_value())
),
AccessControlCreate
);
}
#[qs_test]
async fn test_access_acp_compound_parser(qs: &QueryServer) {
let mut qs_write = qs.write(duration_from_epoch_now()).await.unwrap();
let e = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::AccessControlProfile.to_value()
),
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
(Attribute::Name, Value::new_iname("acp_valid")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpReceiverGroup,
Value::Refer(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(
Attribute::AcpTargetScope,
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
),
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
(Attribute::AcpCreateClass, EntryClass::Class.to_value()),
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
(
Attribute::AcpModifyRemovedAttr,
Value::from(Attribute::Name)
),
(
Attribute::AcpModifyPresentAttr,
Value::from(Attribute::Name)
),
(Attribute::AcpModifyClass, EntryClass::Object.to_value())
);
acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlCreate);
acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlDelete);
acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlModify);
acp_from_entry_ok!(&mut qs_write, e, AccessControlSearch);
}
macro_rules! test_acp_search {
(
$se:expr,
$controls:expr,
$entries:expr,
$expect:expr
) => {{
let ac = AccessControls::default();
let mut acw = ac.write();
acw.update_search($controls).expect("Failed to update");
let acw = acw;
let res = acw
.search_filter_entries(&mut $se, $entries)
.expect("op failed");
debug!("result --> {:?}", res);
debug!("expect --> {:?}", $expect);
assert_eq!(res, $expect);
}};
}
macro_rules! test_acp_search_reduce {
(
$se:expr,
$controls:expr,
$entries:expr,
$expect:expr
) => {{
let ac = AccessControls::default();
let mut acw = ac.write();
acw.update_search($controls).expect("Failed to update");
let acw = acw;
let res = acw
.search_filter_entries(&mut $se, $entries)
.expect("operation failed");
let reduced = acw
.search_filter_entry_attributes(&mut $se, res)
.expect("operation failed");
let expect_set: Vec<Entry<EntryReduced, EntryCommitted>> =
$expect.into_iter().map(|e| e.into_reduced()).collect();
debug!("expect --> {:?}", expect_set);
debug!("result --> {:?}", reduced);
assert_eq!(reduced, expect_set);
}};
}
#[test]
fn test_access_internal_search() {
let se = SearchEvent::new_internal_invalid(filter!(f_pres(Attribute::Class)));
let expect = vec![E_TEST_ACCOUNT_1.clone()];
let entries = vec![E_TEST_ACCOUNT_1.clone()];
test_acp_search!(
&se,
vec![AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_pres(Attribute::NonExist)), Attribute::Name.as_ref(), )],
entries,
expect
);
}
#[test]
fn test_access_enforce_search() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let ev2 = E_TESTPERSON_2.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
let se_a = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let ex_a = vec![Arc::new(ev1)];
let se_b = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let ex_b = vec![];
let acp = AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
Attribute::Name.as_ref(),
);
test_acp_search!(&se_a, vec![acp.clone()], r_set.clone(), ex_a);
test_acp_search!(&se_b, vec![acp], r_set, ex_b);
}
#[test]
fn test_access_enforce_scope_search() {
sketching::test_init();
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let ex_some = vec![Arc::new(ev1.clone())];
let r_set = vec![Arc::new(ev1)];
let se_ro = SearchEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
filter_all!(f_pres(Attribute::Name)),
);
let se_rw = SearchEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
filter_all!(f_pres(Attribute::Name)),
);
let acp = AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
Attribute::Name.as_ref(),
);
test_acp_search!(&se_ro, vec![acp.clone()], r_set.clone(), ex_some);
test_acp_search!(&se_rw, vec![acp], r_set, ex_some);
}
#[test]
fn test_access_enforce_scope_search_attrs() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
let ex_anon_some = vec![exv1];
let se_anon_ro = SearchEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
filter_all!(f_pres(Attribute::Name)),
);
let acp = AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
Attribute::Name.as_ref(),
);
test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
}
lazy_static! {
pub static ref E_TESTPERSON_1_REDUCED: EntryInitNew =
entry_init!((Attribute::Name, Value::new_iname("testperson1")));
}
#[test]
fn test_access_enforce_search_attrs() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
let ex_anon = vec![exv1];
let se_anon = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
Attribute::Name.as_ref(),
);
test_acp_search_reduce!(&se_anon, vec![acp], r_set, ex_anon);
}
#[test]
fn test_access_enforce_search_attrs_req() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let exv1 = E_TESTPERSON_1_REDUCED.clone().into_sealed_committed();
let ex_anon = vec![exv1];
let mut se_anon = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
se_anon.attrs = Some(btreeset![Attribute::Name]);
let acp = AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
"name uuid",
);
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::default();
let mut acw = ac.write();
acw.update_modify($controls).expect("Failed to update");
let acw = acw;
let res = acw
.modify_allow_operation(&mut $me, $entries)
.expect("op failed");
debug!("result --> {:?}", res);
debug!("expect --> {:?}", $expect);
assert_eq!(res, $expect);
}};
(
$me:expr,
$controls:expr,
$sync_uuid:expr,
$sync_yield_attr:expr,
$entries:expr,
$expect:expr
) => {{
let ac = AccessControls::default();
let mut acw = ac.write();
acw.update_modify($controls).expect("Failed to update");
let mut sync_agmt = HashMap::new();
let mut set = BTreeSet::new();
set.insert($sync_yield_attr);
sync_agmt.insert($sync_uuid, set);
acw.update_sync_agreements(sync_agmt);
let acw = acw;
let res = acw
.modify_allow_operation(&mut $me, $entries)
.expect("op failed");
debug!("result --> {:?}", res);
debug!("expect --> {:?}", $expect);
assert_eq!(res, $expect);
}};
}
#[test]
fn test_access_enforce_modify() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let me_pres = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
);
let me_rem = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
);
let me_purge = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_purge(Attribute::Name)]),
);
let me_pres_class = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Class, &EntryClass::Account.to_value())]),
);
let me_rem_class = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_remove(
Attribute::Class,
&EntryClass::Account.to_partialvalue()
)]),
);
let me_purge_class = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_purge(Attribute::Class)]),
);
let acp_allow = AccessControlModify::from_raw(
"test_modify_allow",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
"name class",
"name class",
EntryClass::Account.into(),
);
let acp_deny = AccessControlModify::from_raw(
"test_modify_deny",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
"member class",
"member class",
"group",
);
let acp_no_class = AccessControlModify::from_raw(
"test_modify_no_class",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
"name class",
"name class",
"group",
);
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r_set, true);
test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r_set, true);
test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r_set, true);
test_acp_modify!(&me_pres, vec![acp_deny.clone()], &r_set, false);
test_acp_modify!(&me_rem, vec![acp_deny.clone()], &r_set, false);
test_acp_modify!(&me_purge, vec![acp_deny.clone()], &r_set, false);
test_acp_modify!(&me_pres_class, vec![acp_allow.clone()], &r_set, true);
test_acp_modify!(&me_rem_class, vec![acp_allow.clone()], &r_set, true);
test_acp_modify!(&me_purge_class, vec![acp_allow], &r_set, false);
test_acp_modify!(&me_pres_class, vec![acp_no_class.clone()], &r_set, false);
test_acp_modify!(&me_pres_class, vec![acp_deny.clone()], &r_set, false);
test_acp_modify!(&me_rem_class, vec![acp_no_class], &r_set, false);
test_acp_modify!(&me_rem_class, vec![acp_deny], &r_set, false);
}
#[test]
fn test_access_enforce_scope_modify() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let me_pres_ro = ModifyEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
);
let me_pres_rw = ModifyEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
);
let acp_allow = AccessControlModify::from_raw(
"test_modify_allow",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
"name class",
"name class",
EntryClass::Account.into(),
);
test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
test_acp_modify!(&me_pres_rw, vec![acp_allow], &r_set, true);
}
macro_rules! test_acp_create {
(
$ce:expr,
$controls:expr,
$entries:expr,
$expect:expr
) => {{
let ac = AccessControls::default();
let mut acw = ac.write();
acw.update_create($controls).expect("Failed to update");
let acw = acw;
let res = acw
.create_allow_operation(&mut $ce, $entries)
.expect("op failed");
debug!("result --> {:?}", res);
debug!("expect --> {:?}", $expect);
assert_eq!(res, $expect);
}};
}
#[test]
fn test_access_enforce_create() {
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r1_set = vec![ev1];
let ev2 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::TestNotAllowed, Value::new_class("notallowed")),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r2_set = vec![ev2];
let ev3 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, Value::new_class("notallowed")),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r3_set = vec![ev3];
let ev4 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r4_set = vec![ev4];
let ce_admin = CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
vec![],
);
let acp = AccessControlCreate::from_raw(
"test_create",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
EntryClass::Account.into(),
"class name uuid",
);
let acp2 = AccessControlCreate::from_raw(
"test_create_2",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
EntryClass::Group.into(),
"class name uuid",
);
test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
test_acp_create!(&ce_admin, vec![acp.clone()], &r3_set, false);
test_acp_create!(&ce_admin, vec![acp, acp2], &r4_set, false);
}
#[test]
fn test_access_enforce_scope_create() {
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r1_set = vec![ev1];
let admin = E_TEST_ACCOUNT_1.clone();
let ce_admin_ro = CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readonly(admin.clone()),
vec![],
);
let ce_admin_rw = CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(admin),
vec![],
);
let acp = AccessControlCreate::from_raw(
"test_create",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
EntryClass::Account.into(),
"class name uuid",
);
test_acp_create!(&ce_admin_ro, vec![acp.clone()], &r1_set, false);
test_acp_create!(&ce_admin_rw, vec![acp], &r1_set, true);
}
macro_rules! test_acp_delete {
(
$de:expr,
$controls:expr,
$entries:expr,
$expect:expr
) => {{
let ac = AccessControls::default();
let mut acw = ac.write();
acw.update_delete($controls).expect("Failed to update");
let acw = acw;
let res = acw
.delete_allow_operation($de, $entries)
.expect("op failed");
debug!("result --> {:?}", res);
debug!("expect --> {:?}", $expect);
assert_eq!(res, $expect);
}};
}
#[test]
fn test_access_enforce_delete() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let de_admin = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let de_anon = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlDelete::from_raw(
"test_delete",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
test_acp_delete!(&de_admin, vec![acp.clone()], &r_set, true);
test_acp_delete!(&de_anon, vec![acp], &r_set, false);
}
#[test]
fn test_access_enforce_scope_delete() {
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let admin = E_TEST_ACCOUNT_1.clone();
let de_admin_ro = DeleteEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readonly(admin.clone()),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let de_admin_rw = DeleteEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(admin),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlDelete::from_raw(
"test_delete",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
test_acp_delete!(&de_admin_ro, vec![acp.clone()], &r_set, false);
test_acp_delete!(&de_admin_rw, vec![acp], &r_set, true);
}
macro_rules! test_acp_effective_permissions {
(
$ident:expr,
$attrs:expr,
$search_controls:expr,
$modify_controls:expr,
$entries:expr,
$expect:expr
) => {{
let ac = AccessControls::default();
let mut acw = ac.write();
acw.update_search($search_controls)
.expect("Failed to update");
acw.update_modify($modify_controls)
.expect("Failed to update");
let acw = acw;
let res = acw
.effective_permission_check($ident, $attrs, $entries)
.expect("Failed to apply effective_permission_check");
debug!("result --> {:?}", res);
debug!("expect --> {:?}", $expect);
assert_eq!(res, $expect);
}};
}
#[test]
fn test_access_effective_permission_check_1() {
sketching::test_init();
let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
test_acp_effective_permissions!(
&admin,
None,
vec![AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
Attribute::Name.as_ref(),
)],
vec![],
&r_set,
vec![AccessEffectivePermission {
delete: false,
target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
search: Access::Allow(btreeset![Attribute::Name]),
modify_pres: Access::Allow(BTreeSet::new()),
modify_rem: Access::Allow(BTreeSet::new()),
modify_class: AccessClass::Allow(BTreeSet::new()),
}]
)
}
#[test]
fn test_access_effective_permission_check_2() {
sketching::test_init();
let admin = Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone());
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
test_acp_effective_permissions!(
&admin,
None,
vec![],
vec![AccessControlModify::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
Attribute::Name.as_ref(),
Attribute::Name.as_ref(),
EntryClass::Object.into(),
)],
&r_set,
vec![AccessEffectivePermission {
delete: false,
target: uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
search: Access::Allow(BTreeSet::new()),
modify_pres: Access::Allow(btreeset![Attribute::Name]),
modify_rem: Access::Allow(btreeset![Attribute::Name]),
modify_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
}]
)
}
#[test]
fn test_access_sync_authority_create() {
sketching::test_init();
let ce_admin = CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
vec![],
);
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r1_set = vec![ev1];
let ev2 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::SyncObject.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
);
let r2_set = vec![ev2];
let acp = AccessControlCreate::from_raw(
"test_create",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
"account sync_object",
"class name uuid",
);
test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
test_acp_create!(&ce_admin, vec![acp], &r2_set, false);
}
#[test]
fn test_access_sync_authority_delete() {
sketching::test_init();
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
)
.into_sealed_committed();
let r1_set = vec![Arc::new(ev1)];
let ev2 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::SyncObject.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
)
.into_sealed_committed();
let r2_set = vec![Arc::new(ev2)];
let de_admin = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlDelete::from_raw(
"test_delete",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
test_acp_delete!(&de_admin, vec![acp.clone()], &r1_set, true);
test_acp_delete!(&de_admin, vec![acp], &r2_set, false);
}
#[test]
fn test_access_sync_authority_modify() {
sketching::test_init();
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
)
.into_sealed_committed();
let r1_set = vec![Arc::new(ev1)];
let sync_uuid = Uuid::new_v4();
let ev2 = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::SyncObject.to_value()),
(Attribute::SyncParentUuid, Value::Refer(sync_uuid)),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
)
.into_sealed_committed();
let r2_set = vec![Arc::new(ev2)];
let acp_allow = AccessControlModify::from_raw(
"test_modify_allow",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
&format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
&format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
EntryClass::Account.into(),
);
let me_pres = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(
Attribute::UserAuthTokenSession,
&Value::new_iname("value")
)]),
);
let me_rem = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_remove(
Attribute::UserAuthTokenSession,
&PartialValue::new_iname("value")
)]),
);
let me_purge = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_purge(Attribute::UserAuthTokenSession)]),
);
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r1_set, true);
test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r1_set, true);
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, true);
test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, true);
test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, true);
let me_pres = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
);
let me_rem = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
);
let me_purge = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_purge(Attribute::Name)]),
);
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
test_acp_modify!(&me_rem, vec![acp_allow.clone()], &r2_set, false);
test_acp_modify!(&me_purge, vec![acp_allow.clone()], &r2_set, false);
test_acp_modify!(
&me_pres,
vec![acp_allow.clone()],
sync_uuid,
Attribute::Name,
&r2_set,
true
);
test_acp_modify!(
&me_rem,
vec![acp_allow.clone()],
sync_uuid,
Attribute::Name,
&r2_set,
true
);
test_acp_modify!(
&me_purge,
vec![acp_allow],
sync_uuid,
Attribute::Name,
&r2_set,
true
);
}
#[test]
fn test_access_oauth2_dyn_search() {
sketching::test_init();
let rs_uuid = Uuid::new_v4();
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::OAuth2ResourceServer.to_value()
),
(
Attribute::Class,
EntryClass::OAuth2ResourceServerBasic.to_value()
),
(Attribute::Uuid, Value::Uuid(rs_uuid)),
(Attribute::Name, Value::new_iname("test_resource_server")),
(
Attribute::DisplayName,
Value::new_utf8s("test_resource_server")
),
(
Attribute::OAuth2RsOriginLanding,
Value::new_url_s("https://demo.example.com").unwrap()
),
(
Attribute::OAuth2RsOrigin,
Value::new_url_s("app://hidden").unwrap()
),
(
Attribute::OAuth2RsScopeMap,
Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["groups".to_string()])
.expect("invalid oauthscope")
),
(
Attribute::OAuth2RsSupScopeMap,
Value::new_oauthscopemap(UUID_TEST_GROUP_1, btreeset!["supplement".to_string()])
.expect("invalid oauthscope")
),
(
Attribute::OAuth2AllowInsecureClientDisablePkce,
Value::new_bool(true)
),
(
Attribute::OAuth2JwtLegacyCryptoEnable,
Value::new_bool(false)
),
(Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
)
.into_sealed_committed();
let ev1_reduced = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::OAuth2ResourceServer.to_value()
),
(
Attribute::Class,
EntryClass::OAuth2ResourceServerBasic.to_value()
),
(Attribute::Uuid, Value::Uuid(rs_uuid)),
(Attribute::Name, Value::new_iname("test_resource_server")),
(
Attribute::DisplayName,
Value::new_utf8s("test_resource_server")
),
(
Attribute::OAuth2RsOriginLanding,
Value::new_url_s("https://demo.example.com").unwrap()
)
)
.into_sealed_committed();
let ev2 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(
Attribute::Class,
EntryClass::OAuth2ResourceServer.to_value()
),
(
Attribute::Class,
EntryClass::OAuth2ResourceServerBasic.to_value()
),
(Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
(Attribute::Name, Value::new_iname("second_resource_server")),
(
Attribute::DisplayName,
Value::new_utf8s("second_resource_server")
),
(
Attribute::OAuth2RsOriginLanding,
Value::new_url_s("https://noaccess.example.com").unwrap()
),
(
Attribute::OAuth2RsOrigin,
Value::new_url_s("app://hidden").unwrap()
),
(
Attribute::OAuth2RsScopeMap,
Value::new_oauthscopemap(UUID_SYSTEM_ADMINS, btreeset!["groups".to_string()])
.expect("invalid oauthscope")
),
(
Attribute::OAuth2RsSupScopeMap,
Value::new_oauthscopemap(
UUID_TEST_GROUP_1,
btreeset!["supplement".to_string()]
)
.expect("invalid oauthscope")
),
(
Attribute::OAuth2AllowInsecureClientDisablePkce,
Value::new_bool(true)
),
(
Attribute::OAuth2JwtLegacyCryptoEnable,
Value::new_bool(false)
),
(Attribute::OAuth2PreferShortUsername, Value::new_bool(false))
)
.into_sealed_committed();
let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
let se_a = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let ex_a = vec![Arc::new(ev1)];
let ex_a_reduced = vec![ev1_reduced];
let se_b = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let ex_b = vec![];
test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
test_acp_search!(&se_b, vec![], r_set, ex_b);
}
#[test]
fn test_access_sync_account_dyn_search() {
sketching::test_init();
let sync_uuid = Uuid::new_v4();
let portal_url = Url::parse("https://localhost/portal").unwrap();
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::SyncAccount.to_value()),
(Attribute::Uuid, Value::Uuid(sync_uuid)),
(Attribute::Name, Value::new_iname("test_sync_account")),
(
Attribute::SyncCredentialPortal,
Value::Url(portal_url.clone())
)
)
.into_sealed_committed();
let ev1_reduced = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::SyncAccount.to_value()),
(Attribute::Uuid, Value::Uuid(sync_uuid)),
(
Attribute::SyncCredentialPortal,
Value::Url(portal_url.clone())
)
)
.into_sealed_committed();
let ev2 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::SyncAccount.to_value()),
(Attribute::Uuid, Value::Uuid(Uuid::new_v4())),
(Attribute::Name, Value::new_iname("test_sync_account")),
(
Attribute::SyncCredentialPortal,
Value::Url(portal_url.clone())
)
)
.into_sealed_committed();
let sync_test_account: Arc<EntrySealedCommitted> = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::SyncObject.to_value()),
(Attribute::Name, Value::new_iname("test_account_1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
(Attribute::SyncParentUuid, Value::Refer(sync_uuid))
)
.into_sealed_committed(),
);
let r_set = vec![Arc::new(ev1.clone()), Arc::new(ev2)];
let se_a = SearchEvent::new_impersonate_entry(
sync_test_account,
filter_all!(f_pres(Attribute::SyncCredentialPortal)),
);
let ex_a = vec![Arc::new(ev1)];
let ex_a_reduced = vec![ev1_reduced];
test_acp_search!(&se_a, vec![], r_set.clone(), ex_a);
test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
let se_b = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_pres(Attribute::SyncCredentialPortal)),
);
let ex_b = vec![];
test_acp_search!(&se_b, vec![], r_set, ex_b);
}
#[test]
fn test_access_entry_managed_by_search() {
sketching::test_init();
let test_entry = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed(),
);
let data_set = vec![test_entry.clone()];
let se_a = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let expect_a = vec![test_entry];
let se_b = SearchEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_pres(Attribute::Name)),
);
let expect_b = vec![];
let acp = AccessControlSearch::from_managed_by(
"test_acp",
Uuid::new_v4(),
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
Attribute::Name.as_ref(),
);
test_acp_search!(&se_a, vec![acp.clone()], data_set.clone(), expect_a);
test_acp_search!(&se_b, vec![acp], data_set, expect_b);
}
#[test]
fn test_access_entry_managed_by_create() {
sketching::test_init();
let test_entry = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
);
let data_set = vec![test_entry];
let ce = CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
vec![],
);
let acp = AccessControlCreate::from_managed_by(
"test_create",
Uuid::new_v4(),
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
EntryClass::Account.into(),
"class name uuid",
);
test_acp_create!(&ce, vec![acp.clone()], &data_set, false);
}
#[test]
fn test_access_entry_managed_by_modify() {
let test_entry = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed(),
);
let data_set = vec![test_entry];
let me_pres = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]),
);
let me_rem = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]),
);
let me_purge = ModifyEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
modlist!([m_purge(Attribute::Name)]),
);
let acp_allow = AccessControlModify::from_managed_by(
"test_modify_allow",
Uuid::new_v4(),
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
"name class",
"name class",
EntryClass::Account.into(),
);
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &data_set, true);
test_acp_modify!(&me_rem, vec![acp_allow.clone()], &data_set, true);
test_acp_modify!(&me_purge, vec![acp_allow.clone()], &data_set, true);
}
#[test]
fn test_access_entry_managed_by_delete() {
let test_entry = Arc::new(
entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::EntryManagedBy, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed(),
);
let data_set = vec![test_entry];
let de_a = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let de_b = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_2.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlDelete::from_managed_by(
"test_delete",
Uuid::new_v4(),
AccessControlTarget::Scope(filter_valid!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
))),
);
test_acp_delete!(&de_a, vec![acp.clone()], &data_set, true);
test_acp_delete!(&de_b, vec![acp], &data_set, false);
}
#[test]
fn test_access_delete_protect_system_ranges() {
let ev1: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into();
let ev1 = ev1.into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let de_account = DeleteEvent::new_impersonate_entry(
E_TEST_ACCOUNT_1.clone(),
filter_all!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
);
let acp = AccessControlDelete::from_raw(
"test_delete",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(Attribute::Name, PartialValue::new_iname("anonymous"))),
);
test_acp_delete!(&de_account, vec![acp], &r_set, false);
}
#[test]
fn test_access_sync_memberof_implies_directmemberof() {
sketching::test_init();
let ev1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Name, Value::new_iname("test_account_1")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1)),
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
(Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
let exv1 = entry_init!(
(Attribute::Name, Value::new_iname("test_account_1")),
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP_1)),
(Attribute::DirectMemberOf, Value::Refer(UUID_TEST_GROUP_1))
)
.into_sealed_committed();
let ex_anon_some = vec![exv1];
let se_anon_ro = SearchEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readonly(E_TEST_ACCOUNT_1.clone()),
filter_all!(f_pres(Attribute::Name)),
);
let acp = AccessControlSearch::from_raw(
"test_acp",
Uuid::new_v4(),
UUID_TEST_GROUP_1,
filter_valid!(f_eq(
Attribute::Uuid,
PartialValue::Uuid(UUID_TEST_ACCOUNT_1)
)),
format!("{} {}", Attribute::Name, Attribute::MemberOf).as_str(),
);
test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
}
}