kanidm/server/lib/src/plugins/dyngroup.rs

999 lines
37 KiB
Rust

use std::collections::BTreeMap;
use std::sync::Arc;
use kanidm_proto::v1::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,
candidate_tuples: &mut Vec<(Arc<EntrySealedCommitted>, EntryInvalidCommitted)>,
affected_uuids: &mut Vec<Uuid>,
expect: bool,
ident_internal: &Identity,
dyn_groups: &mut DynGroupCache,
n_dyn_groups: &[&Entry<EntrySealed, EntryCommitted>],
) -> Result<(), OperationError> {
/*
* This triggers even if we are modifying the dyngroups account policy attributes, which
* is allowed now. So we relax this, because systemprotection still blocks the creation
* of dyngroups.
if !ident.is_internal() {
// It should be impossible to trigger this right now due to protected plugin.
error!("It is currently an error to create a dynamic group");
return Err(OperationError::SystemProtectedObject);
}
*/
if qs.get_phase() < ServerPhase::SchemaReady {
trace!("Server is not ready to load dyngroups");
return Ok(());
}
// Search all the new groups first.
let filt = filter!(FC::Or(
n_dyn_groups
.iter()
.map(|e| f_eq(Attribute::Uuid, PartialValue::Uuid(e.get_uuid())))
.collect()
));
let work_set = qs.internal_search_writeable(&filt)?;
// Go through them all and update the new groups.
for (pre, mut nd_group) in work_set.into_iter() {
let scope_f: ProtoFilter = nd_group
.get_ava_single_protofilter(Attribute::DynGroupFilter)
.cloned()
.ok_or_else(|| {
admin_error!("Missing {}", Attribute::DynGroupFilter);
OperationError::InvalidEntryState
})?;
let scope_i = Filter::from_rw(ident_internal, &scope_f, qs).map_err(|e| {
admin_error!("{} validation failed {:?}", Attribute::DynGroupFilter, e);
e
})?;
let uuid = pre.get_uuid();
// Add our uuid as affected.
affected_uuids.push(uuid);
// Apply the filter and get all the uuids.
let entries = qs.internal_search(scope_i.clone()).map_err(|e| {
admin_error!("internal search failure -> {:?}", e);
e
})?;
let members = ValueSetRefer::from_iter(entries.iter().map(|e| e.get_uuid()));
if let Some(uuid_iter) = members.as_ref().and_then(|a| a.as_ref_uuid_iter()) {
affected_uuids.extend(uuid_iter);
}
// Mark the former members as being affected also.
if let Some(uuid_iter) = pre.get_ava_as_refuuid(Attribute::DynMember) {
affected_uuids.extend(uuid_iter);
}
if let Some(members) = members {
// Only set something if there is actually something to do!
nd_group.set_ava_set(Attribute::DynMember, members);
// push the entries to pre/cand
} else {
nd_group.purge_ava(Attribute::DynMember);
}
candidate_tuples.push((pre, nd_group));
// Insert to our new instances
if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
admin_error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid);
return Err(OperationError::InvalidState);
}
}
Ok(())
}
#[instrument(level = "debug", name = "dyngroup::reload", skip_all)]
pub fn reload(qs: &mut QueryServerWriteTransaction) -> Result<(), OperationError> {
let ident_internal = Identity::from_internal();
// Internal search all our definitions.
let filt = filter!(f_eq(Attribute::Class, EntryClass::DynGroup.into()));
let entries = qs.internal_search(filt).map_err(|e| {
admin_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(|| {
admin_error!("Missing {}", Attribute::DynGroupFilter);
OperationError::InvalidEntryState
})?;
let scope_i = Filter::from_rw(&ident_internal, &scope_f, qs).map_err(|e| {
admin_error!("dyngroup_filter validation failed {:?}", e);
e
})?;
let uuid = nd_group.get_uuid();
if reload_groups.insert(uuid, scope_i).is_some() {
admin_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<Vec<Uuid>, OperationError> {
let mut affected_uuids = Vec::with_capacity(cand.len());
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())
});
// DANGER: Why do we have to do this? During the use of qs for internal search
// and other operations we need qs to be mut. But when we borrow dyn groups here we
// cause multiple borrows to occur on struct members that freaks rust out. This *IS*
// safe however because no element of the search or write process calls the dyngroup
// cache excepting for this plugin within a single thread, meaning that stripping the
// lifetime here is safe since we are the sole accessor.
let dyn_groups: &mut DynGroupCache = unsafe { &mut *(qs.get_dyngroup_cache() as *mut _) };
// For any other entries, check if they SHOULD trigger
// a dyn group inclusion. We do this FIRST because the new
// dyn groups will see the created entries on an internal search
// so we don't need to reference them.
let mut candidate_tuples = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
// Apply existing dyn_groups to entries.
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()))
})?;
// Did any of our modified entries match our dyn group filter?
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 any of them did, we retrieve the dyngroup and setup to write the new
// members to it.
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)));
affected_uuids.extend(matches.into_iter());
affected_uuids.push(*dg_uuid);
candidate_tuples.push((pre, d_group));
}
}
}
// If we created any dyn groups, populate them now.
// if the event is not internal, reject (for now)
if !n_dyn_groups.is_empty() {
trace!("considering new dyngroups");
Self::apply_dyngroup_change(
qs,
&mut candidate_tuples,
&mut affected_uuids,
false,
&ident_internal,
dyn_groups,
n_dyn_groups.as_slice(),
)?;
}
// Write back the new changes.
// Write this stripe if populated.
if !candidate_tuples.is_empty() {
qs.internal_apply_writable(candidate_tuples).map_err(|e| {
admin_error!("Failed to commit dyngroup set {:?}", e);
e
})?;
}
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<Vec<Uuid>, OperationError> {
let mut affected_uuids = Vec::with_capacity(cand.len());
let ident_internal = Identity::from_internal();
// Probably should be filter here instead.
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())
});
// DANGER: Why do we have to do this? During the use of qs for internal search
// and other operations we need qs to be mut. But when we borrow dyn groups here we
// cause multiple borrows to occur on struct members that freaks rust out. This *IS*
// safe however because no element of the search or write process calls the dyngroup
// cache excepting for this plugin within a single thread, meaning that stripping the
// lifetime here is safe since we are the sole accessor.
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 we modified a dyngroups member or filter, re-trigger it here.
// if the event is not internal, reject (for now)
// We do this *first* so that we don't accidentally include/exclude anything that
// changed in this op.
if !n_dyn_groups.is_empty() {
trace!("considering modified dyngroups");
Self::apply_dyngroup_change(
qs,
&mut candidate_tuples,
&mut affected_uuids,
true,
&ident_internal,
dyn_groups,
n_dyn_groups.as_slice(),
)?;
}
// If we modified anything else, check if a dyngroup is affected by it's change
// if it was a member.
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);
// There are some cases where rather than the optimisation to skip
// asserting membership, we need to always assert that membership. Generally
// this occurs in replication where if a candidate was conflicted it can
// trigger a membership delete, but we need to ensure it's still re-added.
if post_t && (force_cand_updates || !pre_t) {
// The entry was added
Some(Ok(post.get_uuid()))
} else if pre_t && !post_t {
// The entry was deleted
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)),
});
affected_uuids.extend(matches.into_iter().map(|choice| match choice {
Ok(u) => u,
Err(u) => u,
}));
affected_uuids.push(*dg_uuid);
candidate_tuples.push((pre, d_group));
}
}
}
// Write back the new changes.
// Write this stripe if populated.
if !candidate_tuples.is_empty() {
qs.internal_apply_writable(candidate_tuples).map_err(|e| {
admin_error!("Failed to commit dyngroup set {:?}", e);
e
})?;
}
trace!(?affected_uuids);
Ok(affected_uuids)
}
// No post_delete handler is needed as refint takes care of this for us.
pub fn verify(_qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
vec![]
}
}
#[cfg(test)]
mod tests {
use kanidm_proto::v1::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,
// Need to validate it did things
|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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert!(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,
// Need to validate it did things
|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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert!(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,
// Need to validate it did things
|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.get(0).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,
// Need to validate it did things
|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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert!(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.into(),
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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert!(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.into(),
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.get(0).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.into(),
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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
// We assert to refer single here because we should have "removed" uuid_admin being added
// at all.
assert!(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.into(),)]),
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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
// We assert to refer single here because we should have re-added the members
assert!(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.into(),),
Modify::Present(Attribute::Name.into(), 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.get(0).expect("Unable to access group.");
let members = d_group
.get_ava_set(Attribute::DynMember)
.expect("No members on dyn group");
assert!(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.into(),),
Modify::Present(Attribute::Name.into(), 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.get(0).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.get(0).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| {
// Note we check memberof is empty here!
let cands = qs
.internal_search(filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testgroup")
)))
.expect("Internal search failure");
let d_group = cands.get(0).expect("Unable to access group.");
assert!(d_group.get_ava_set(Attribute::MemberOf).is_none());
}
);
}
}