use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use kanidm_proto::internal::Filter as ProtoFilter;
use crate::filter::FilterInvalid;
use crate::prelude::*;
use crate::server::ServerPhase;
#[derive(Clone, Default)]
pub struct DynGroupCache {
insts: BTreeMap<Uuid, Filter<FilterInvalid>>,
}
pub struct DynGroup;
impl DynGroup {
#[allow(clippy::too_many_arguments)]
fn apply_dyngroup_change(
qs: &mut QueryServerWriteTransaction,
affected_uuids: &mut BTreeSet<Uuid>,
expect: bool,
ident_internal: &Identity,
dyn_groups: &mut DynGroupCache,
n_dyn_groups: &[&Entry<EntrySealed, EntryCommitted>],
) -> Result<(), OperationError> {
if qs.get_phase() < ServerPhase::SchemaReady {
debug!("Server is not ready to load dyngroups");
return Ok(());
}
let filt = filter!(FC::Or(
n_dyn_groups
.iter()
.map(|e| f_eq(Attribute::Uuid, PartialValue::Uuid(e.get_uuid())))
.collect()
));
let mut work_set = qs.internal_search_writeable(&filt)?;
for (ref pre, ref mut nd_group) in work_set.iter_mut() {
trace!(dyngroup_id = %nd_group.get_display_id());
let scope_f: ProtoFilter = nd_group
.get_ava_single_protofilter(Attribute::DynGroupFilter)
.cloned()
.ok_or_else(|| {
error!("Missing {}", Attribute::DynGroupFilter);
OperationError::InvalidEntryState
})?;
let scope_i = Filter::from_rw(ident_internal, &scope_f, qs).map_err(|e| {
error!("{} validation failed {:?}", Attribute::DynGroupFilter, e);
e
})?;
trace!(dyngroup_filter = ?scope_i);
let uuid = pre.get_uuid();
affected_uuids.insert(uuid);
let entries = qs.internal_search(scope_i.clone()).map_err(|e| {
error!("internal search failure -> {:?}", e);
e
})?;
trace!(entries_len = %entries.len());
let members = ValueSetRefer::from_iter(entries.iter().map(|e| e.get_uuid()));
trace!(?members);
if let Some(uuid_iter) = members.as_ref().and_then(|a| a.as_ref_uuid_iter()) {
affected_uuids.extend(uuid_iter);
}
if let Some(uuid_iter) = pre.get_ava_as_refuuid(Attribute::DynMember) {
affected_uuids.extend(uuid_iter);
}
if let Some(members) = members {
nd_group.set_ava_set(&Attribute::DynMember, members);
} else {
nd_group.purge_ava(Attribute::DynMember);
}
if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid);
return Err(OperationError::InvalidState);
}
}
if !work_set.is_empty() {
qs.internal_apply_writable(work_set).map_err(|e| {
error!("Failed to commit dyngroup set {:?}", e);
e
})?;
}
Ok(())
}
#[instrument(level = "debug", name = "dyngroup::reload", skip_all)]
pub fn reload(qs: &mut QueryServerWriteTransaction) -> Result<(), OperationError> {
let ident_internal = Identity::from_internal();
let filt = filter!(f_eq(Attribute::Class, EntryClass::DynGroup.into()));
let entries = qs.internal_search(filt).map_err(|e| {
error!("internal search failure -> {:?}", e);
e
})?;
let mut reload_groups = BTreeMap::default();
for nd_group in entries.into_iter() {
let scope_f: ProtoFilter = nd_group
.get_ava_single_protofilter(Attribute::DynGroupFilter)
.cloned()
.ok_or_else(|| {
error!("Missing {}", Attribute::DynGroupFilter);
OperationError::InvalidEntryState
})?;
let scope_i = Filter::from_rw(&ident_internal, &scope_f, qs).map_err(|e| {
error!("dyngroup_filter validation failed {:?}", e);
e
})?;
let uuid = nd_group.get_uuid();
if reload_groups.insert(uuid, scope_i).is_some() {
error!("dyngroup cache uuid conflict {}", uuid);
return Err(OperationError::InvalidState);
}
}
let dyn_groups = qs.get_dyngroup_cache();
std::mem::swap(&mut reload_groups, &mut dyn_groups.insts);
Ok(())
}
#[instrument(level = "debug", name = "dyngroup::post_create", skip_all)]
pub fn post_create(
qs: &mut QueryServerWriteTransaction,
cand: &[Entry<EntrySealed, EntryCommitted>],
_ident: &Identity,
) -> Result<BTreeSet<Uuid>, OperationError> {
let mut affected_uuids = BTreeSet::new();
let ident_internal = Identity::from_internal();
let (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| {
entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
});
let dyn_groups: &mut DynGroupCache = unsafe { &mut *(qs.get_dyngroup_cache() as *mut _) };
let mut candidate_tuples = Vec::with_capacity(cand.len());
trace!(?dyn_groups.insts);
for (dg_uuid, dg_filter) in dyn_groups.insts.iter() {
let dg_filter_valid = dg_filter
.validate(qs.get_schema())
.map_err(OperationError::SchemaViolation)
.and_then(|f| {
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
})?;
let matches: Vec<_> = entries
.iter()
.filter_map(|e| {
if e.entry_match_no_index(&dg_filter_valid) {
Some(e.get_uuid())
} else {
None
}
})
.collect();
if !matches.is_empty() {
let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(*dg_uuid)));
let mut work_set = qs.internal_search_writeable(&filt)?;
if let Some((pre, mut d_group)) = work_set.pop() {
matches
.iter()
.copied()
.for_each(|u| d_group.add_ava(Attribute::DynMember, Value::Refer(u)));
let pre_dynmember = pre.get_ava_refer(Attribute::DynMember);
let post_dynmember = d_group.get_ava_refer(Attribute::DynMember);
match (pre_dynmember, post_dynmember) {
(Some(pre_m), Some(post_m)) => {
affected_uuids.extend(pre_m.symmetric_difference(post_m));
}
(Some(members), None) | (None, Some(members)) => {
affected_uuids.extend(members);
}
(None, None) => {}
};
candidate_tuples.push((pre, d_group));
}
}
}
if !candidate_tuples.is_empty() {
qs.internal_apply_writable(candidate_tuples).map_err(|e| {
error!("Failed to commit dyngroup set {:?}", e);
e
})?;
}
if !n_dyn_groups.is_empty() {
trace!("considering new dyngroups");
Self::apply_dyngroup_change(
qs,
&mut affected_uuids,
false,
&ident_internal,
dyn_groups,
n_dyn_groups.as_slice(),
)?;
}
Ok(affected_uuids)
}
#[instrument(level = "debug", name = "dyngroup::post_modify", skip_all)]
pub fn post_modify(
qs: &mut QueryServerWriteTransaction,
pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
cand: &[Entry<EntrySealed, EntryCommitted>],
_ident: &Identity,
force_cand_updates: bool,
) -> Result<BTreeSet<Uuid>, OperationError> {
let mut affected_uuids = BTreeSet::new();
let ident_internal = Identity::from_internal();
let (_, pre_entries): (Vec<&Arc<Entry<_, _>>>, Vec<_>) =
pre_cand.iter().partition(|entry| {
entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
});
let (n_dyn_groups, post_entries): (Vec<&Entry<_, _>>, Vec<_>) =
cand.iter().partition(|entry| {
entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
});
let dyn_groups: &mut DynGroupCache = unsafe { &mut *(qs.get_dyngroup_cache() as *mut _) };
let mut candidate_tuples = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
if !n_dyn_groups.is_empty() {
Self::apply_dyngroup_change(
qs,
&mut affected_uuids,
true,
&ident_internal,
dyn_groups,
n_dyn_groups.as_slice(),
)?;
}
trace!(?force_cand_updates, ?dyn_groups.insts);
for (dg_uuid, dg_filter) in dyn_groups.insts.iter() {
let dg_filter_valid = dg_filter
.validate(qs.get_schema())
.map_err(OperationError::SchemaViolation)
.and_then(|f| {
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
})?;
let matches: Vec<_> = pre_entries
.iter()
.zip(post_entries.iter())
.filter_map(|(pre, post)| {
let pre_t = pre.entry_match_no_index(&dg_filter_valid);
let post_t = post.entry_match_no_index(&dg_filter_valid);
trace!(?post_t, ?force_cand_updates, ?pre_t);
if post_t && (force_cand_updates || !pre_t) {
Some(Ok(post.get_uuid()))
} else if pre_t && !post_t {
Some(Err(post.get_uuid()))
} else {
None
}
})
.collect();
trace!(?matches);
if !matches.is_empty() {
let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(*dg_uuid)));
let mut work_set = qs.internal_search_writeable(&filt)?;
if let Some((pre, mut d_group)) = work_set.pop() {
matches.iter().copied().for_each(|choice| match choice {
Ok(u) => d_group.add_ava(Attribute::DynMember, Value::Refer(u)),
Err(u) => d_group.remove_ava(Attribute::DynMember, &PartialValue::Refer(u)),
});
let pre_dynmember = pre.get_ava_refer(Attribute::DynMember);
let post_dynmember = d_group.get_ava_refer(Attribute::DynMember);
match (pre_dynmember, post_dynmember) {
(Some(pre_m), Some(post_m)) => {
affected_uuids.extend(pre_m.symmetric_difference(post_m));
}
(Some(members), None) | (None, Some(members)) => {
affected_uuids.extend(members);
}
(None, None) => {}
};
candidate_tuples.push((pre, d_group));
}
}
}
trace!(candidate_tuples_len = %candidate_tuples.len());
if !candidate_tuples.is_empty() {
qs.internal_apply_writable(candidate_tuples).map_err(|e| {
error!("Failed to commit dyngroup set {:?}", e);
e
})?;
}
trace!(?affected_uuids);
Ok(affected_uuids)
}
pub fn verify(_qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
vec![]
}
}
#[cfg(test)]
mod tests {
use kanidm_proto::internal::Filter as ProtoFilter;
use crate::prelude::*;
const UUID_TEST_GROUP: Uuid = uuid::uuid!("7bfd9931-06c2-4608-8a46-78719bb746fe");
#[test]
fn test_create_dyngroup_add_new_group() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_group];
let create = vec![e_dyn];
run_create_test!(
Ok(()),
preload,
create,
None,
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
}
);
}
#[test]
fn test_create_dyngroup_add_matching_entry() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn];
let create = vec![e_group];
run_create_test!(
Ok(()),
preload,
create,
None,
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
}
);
}
#[test]
fn test_create_dyngroup_add_non_matching_entry() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"no_possible_match_to_be_found".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn];
let create = vec![e_group];
run_create_test!(
Ok(()),
preload,
create,
None,
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
}
);
}
#[test]
fn test_create_dyngroup_add_matching_entry_and_group() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![];
let create = vec![e_dyn, e_group];
run_create_test!(
Ok(()),
preload,
create,
None,
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
assert!(d_group.get_ava_set(Attribute::Member).is_none());
}
);
}
#[test]
fn test_modify_dyngroup_existing_dyngroup_filter_into_scope() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"no_such_entry_exists".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)),
ModifyList::new_list(vec![
Modify::Purged("dyngroup_filter".into()),
Modify::Present(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
}
);
}
#[test]
fn test_modify_dyngroup_existing_dyngroup_filter_outof_scope() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)),
ModifyList::new_list(vec![
Modify::Purged("dyngroup_filter".into()),
Modify::Present(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"no_such_entry_exists".to_string()
))
)
]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
}
);
}
#[test]
fn test_modify_dyngroup_existing_dyngroup_member_add() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)),
ModifyList::new_list(vec![Modify::Present(
Attribute::DynMember,
Value::Refer(UUID_ADMIN)
)]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
}
);
}
#[test]
fn test_modify_dyngroup_existing_dyngroup_member_remove() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)),
ModifyList::new_list(vec![Modify::Purged(Attribute::DynMember,)]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
}
);
}
#[test]
fn test_modify_dyngroup_into_matching_entry() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("not_testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("not_testgroup")
)),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Name,),
Modify::Present(Attribute::Name, Value::new_iname("testgroup"))
]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert_eq!(members.to_refer_single(), Some(UUID_TEST_GROUP));
}
);
}
#[test]
fn test_modify_dyngroup_into_non_matching_entry() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testgroup"))),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Name,),
Modify::Present(Attribute::Name, Value::new_iname("not_testgroup"))
]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
}
);
}
#[test]
fn test_delete_dyngroup_matching_entry() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testgroup"))),
None,
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
assert!(d_group.get_ava_set(Attribute::DynMember).is_none());
}
);
}
#[test]
fn test_delete_dyngroup_group() {
let e_dyn = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Class, EntryClass::DynGroup.to_value()),
(Attribute::Name, Value::new_iname("test_dyngroup")),
(
Attribute::DynGroupFilter,
Value::JsonFilt(ProtoFilter::Eq(
Attribute::Name.to_string(),
"testgroup".to_string()
))
)
);
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP))
);
let preload = vec![e_dyn, e_group];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("test_dyngroup")
)),
None,
|qs: &mut QueryServerWriteTransaction| {
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testgroup")
)))
.expect("Internal search failure");
let d_group = cands.first().expect("Unable to access group.");
assert!(d_group.get_ava_set(Attribute::MemberOf).is_none());
}
);
}
}