kanidm/server/lib/src/macros.rs
Firstyear bf1e9b0989
Make schema indexing a boolean instead of index types ()
Previously on schema definitions for attributes, the list of index
types was manually set on attributes. The issue with this approach is
that not all index types apply to all attribute syntaxes. This made it
error prone not just to Kanidm developers, but to future users who
want to define custom attributes and may incorrectly index those
attributes.

Instead, this changes the index value to be a boolean to indicate
if this attribute should or should not be indexed. Internally Kanidm
has a list of appropriate indexes to apply to these syntax types.

As part of this change, the tests were reviewed to find missing index
types for syntaxes, and other causes of unindexed searches which led
to some changes around the dyngroup plugin (which pushes the boundaries
of a lot of things in Kani due to how it works).
2025-03-21 02:13:54 +00:00

645 lines
16 KiB
Rust

#[cfg(test)]
macro_rules! setup_test {
() => {{
sketching::test_init();
// Create an in memory BE
let schema_outer = Schema::new().expect("Failed to init schema");
let idxmeta = {
let schema_txn = schema_outer.write_blocking();
schema_txn.reload_idxmeta()
};
let be = Backend::new(BackendConfig::new_test("main"), idxmeta, false)
.expect("Failed to init BE");
let qs = QueryServer::new(be, schema_outer, "example.com".to_string(), Duration::ZERO)
.expect("Failed to setup Query Server");
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.initialise_helper(duration_from_epoch_now(), DOMAIN_TGT_LEVEL))
.expect("init failed!");
qs
}};
(
$preload_entries:expr
) => {{
use crate::prelude::duration_from_epoch_now;
sketching::test_init();
// Create an in memory BE
let schema_outer = Schema::new().expect("Failed to init schema");
let idxmeta = {
let schema_txn = schema_outer.write();
schema_txn.reload_idxmeta()
};
let be = Backend::new(BackendConfig::new_test("main"), idxmeta, false)
.expect("Failed to init BE");
let qs = QueryServer::new(be, schema_outer, "example.com".to_string(), Duration::ZERO)
.expect("Failed to setup Query Server");
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.initialise_helper(duration_from_epoch_now(), DOMAIN_TGT_LEVEL))
.expect("init failed!");
if !$preload_entries.is_empty() {
let mut qs_write = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.write(duration_from_epoch_now()))
.expect("txn");
qs_write
.internal_create($preload_entries)
.expect("Failed to preload entries");
assert!(qs_write.commit().is_ok());
}
qs
}};
}
// Test helpers for all plugins.
// #[macro_export]
#[cfg(test)]
macro_rules! run_create_test {
(
$expect:expr,
$preload_entries:ident,
$create_entries:ident,
$internal:expr,
$check:expr
) => {{
use crate::be::{Backend, BackendConfig};
use crate::event::CreateEvent;
use crate::prelude::*;
use crate::schema::Schema;
let qs = setup_test!($preload_entries);
let ce = match $internal {
None => CreateEvent::new_internal($create_entries.clone()),
Some(ent) => CreateEvent::new_impersonate_identity(
Identity::from_impersonate_entry_readwrite(ent),
$create_entries.clone(),
),
};
{
let mut qs_write = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.write(duration_from_epoch_now()))
.unwrap();
let r = qs_write.create(&ce);
trace!("test result: {:?}", r);
assert_eq!(r, $expect);
$check(&mut qs_write);
match r {
Ok(_) => {
qs_write.commit().expect("commit failure!");
}
Err(e) => {
admin_error!("Rolling back => {:?}", e);
}
}
}
// Make sure there are no errors.
trace!("starting verification");
let ver = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.verify());
trace!("verification -> {:?}", ver);
assert_eq!(ver.len(), 0);
}};
}
#[cfg(test)]
/// Runs a test with preloaded entries, then modifies based on a filter/list, then runs a given check
macro_rules! run_modify_test {
(
// expected outcome
$expect:expr,
// things to preload
$preload_entries:ident,
// the targets to modify
$modify_filter:expr,
// changes to make
$modify_list:expr,
$internal:expr,
// something to run after the preload but before the modification, takes `&mut qs_write`
$pre_hook:expr,
// the result we expect
$check:expr
) => {{
use crate::be::{Backend, BackendConfig};
use crate::event::ModifyEvent;
use crate::prelude::*;
use crate::schema::Schema;
let qs = setup_test!($preload_entries);
{
let mut qs_write = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.write(duration_from_epoch_now()))
.expect("txn");
$pre_hook(&mut qs_write);
qs_write.commit().expect("commit failure!");
}
let me = match $internal {
None => ModifyEvent::new_internal_invalid($modify_filter, $modify_list),
Some(ent) => ModifyEvent::new_impersonate_entry(ent, $modify_filter, $modify_list),
};
{
let mut qs_write = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.write(duration_from_epoch_now()))
.expect("txn");
let r = qs_write.modify(&me);
$check(&mut qs_write);
trace!("test result: {:?}", r);
assert_eq!(r, $expect);
match r {
Ok(_) => {
qs_write.commit().expect("commit failure!");
}
Err(e) => {
admin_error!("Rolling back => {:?}", e);
}
}
}
// Make sure there are no errors.
trace!("starting verification");
let ver = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.verify());
trace!("verification -> {:?}", ver);
assert_eq!(ver.len(), 0);
}};
}
// #[macro_export]
#[cfg(test)]
macro_rules! run_delete_test {
(
$expect:expr,
$preload_entries:ident,
$delete_filter:expr,
$internal:expr,
$check:expr
) => {{
use crate::be::{Backend, BackendConfig};
use crate::event::DeleteEvent;
use crate::prelude::*;
use crate::schema::Schema;
let qs = setup_test!($preload_entries);
let de = match $internal {
Some(ent) => DeleteEvent::new_impersonate_entry(ent, $delete_filter.clone()),
None => DeleteEvent::new_internal_invalid($delete_filter.clone()),
};
{
let mut qs_write = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.write(duration_from_epoch_now()))
.expect("txn");
let r = qs_write.delete(&de);
trace!("test result: {:?}", r);
$check(&mut qs_write);
assert_eq!(r, $expect);
match r {
Ok(_) => {
qs_write.commit().expect("commit failure!");
}
Err(e) => {
admin_error!("Rolling back => {:?}", e);
}
}
}
// Make sure there are no errors.
trace!("starting verification");
let ver = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(qs.verify());
trace!("verification -> {:?}", ver);
assert_eq!(ver.len(), 0);
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! modlist {
(
$vs:expr
) => {{
let s: Box<[Modify]> = Box::new($vs);
ModifyList::new_list(s.into_vec())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! f_and {
(
$vs:expr
) => {{
let s: Box<[FC]> = Box::new($vs);
f_and(s.into_vec())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! f_inc {
(
$vs:expr
) => {{
let s: Box<[FC]> = Box::new($vs);
f_inc(s.into_vec())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! f_or {
(
$vs:expr
) => {{
let s: Box<[FC]> = Box::new($vs);
f_or(s.into_vec())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! filter {
(
$fc:expr
) => {{
Filter::new_ignore_hidden($fc)
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! filter_rec {
(
$fc:expr
) => {{
Filter::new_recycled($fc)
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! filter_all {
(
$fc:expr
) => {{
Filter::new($fc)
}};
}
#[macro_export]
/// Build a filter which matches class == input
macro_rules! match_class_filter {
($class:expr) => {
ProtoFilter::Eq(Attribute::Class.to_string(), $class.to_string())
};
}
#[cfg(test)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! filter_valid {
(
$fc:expr
) => {{
let f: Filter<FilterInvalid> = Filter::new($fc);
// Create a resolved filter, via the most unsafe means possible!
f.into_valid()
}};
}
#[cfg(test)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! filter_resolved {
(
$fc:expr
) => {{
let f: Filter<FilterInvalid> = Filter::new($fc);
// Create a resolved filter, via the most unsafe means possible!
f.into_valid_resolved()
}};
}
#[cfg(test)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! pvalue_utf8 {
(
$v:expr
) => {{
PartialValue::new_utf8(v.to_string())
}};
}
#[cfg(test)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! pvalue_iutf8 {
(
$v:expr
) => {{
PartialValue::new_iutf8(v.to_string())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! btreeset {
() => (
compile_error!("BTreeSet needs at least 1 element")
);
($e:expr) => ({
use std::collections::BTreeSet;
let mut x: BTreeSet<_> = BTreeSet::new();
assert!(x.insert($e));
x
});
($e:expr,) => ({
btreeset!($e)
});
($e:expr, $($item:expr),*) => ({
use std::collections::BTreeSet;
let mut x: BTreeSet<_> = BTreeSet::new();
assert!(x.insert($e));
$(assert!(x.insert($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! smolset {
() => (
compile_error!("SmolSet needs at least 1 element")
);
($e:expr) => ({
use smolset::SmolSet;
let mut x: SmolSet<_> = SmolSet::new();
assert!(x.insert($e));
x
});
($e:expr,) => ({
smolset!($e)
});
($e:expr, $($item:expr),*) => ({
use smolset::SmolSet;
let mut x: SmolSet<_> = SmolSet::new();
assert!(x.insert($e));
$(assert!(x.insert($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! btreemap {
() => (
compile_error!("BTreeSet needs at least 1 element")
);
($e:expr) => ({
use std::collections::BTreeMap;
let mut x: BTreeMap<_, _> = BTreeMap::new();
let (a, b) = $e;
x.insert(a, b);
x
});
($e:expr,) => ({
btreemap!($e)
});
($e:expr, $($item:expr),*) => ({
use std::collections::BTreeMap;
let mut x: BTreeMap<_, _> = BTreeMap::new();
let (a, b) = $e;
x.insert(a, b);
$(
let (a, b) = $item;
x.insert(a, b);
)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! entry_init {
() => ({
let e1: Entry<EntryInit, EntryNew> = Entry::new();
e1
});
($ava:expr) => ({
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava($ava.0, $ava.1);
e1
});
($ava:expr, $($item:expr),*) => ({
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava($ava.0, $ava.1);
$(e1.add_ava($item.0, $item.1);)*
e1
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! mergesets {
(
$a:expr,
$b:expr
) => {{
$b.iter().for_each(|v| {
$a.insert(v.clone());
});
Ok(())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! mergemaps {
(
$a:expr,
$b:expr
) => {{
$b.iter().for_each(|(k, v)| {
// I think to be consistent, we need the content of b to always
// the content of a
// if !$a.contains_key(k) {
$a.insert(k.clone(), v.clone());
// }
});
Ok(())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_utf8 {
() => (
compile_error!("ValueSetUtf8 needs at least 1 element")
);
($e:expr) => ({
ValueSetUtf8::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetUtf8::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
/// Takes EntryClass objects and makes a ValueSetIutf8
macro_rules! vs_iutf8 {
() => (
compile_error!("ValueSetIutf8 needs at least 1 element")
);
($e:expr) => ({
ValueSetIutf8::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetIutf8::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_iname {
() => (
compile_error!("ValueSetIname needs at least 1 element")
);
($e:expr) => ({
ValueSetIname::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetIname::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_uuid {
() => (
compile_error!("ValueSetUuid needs at least 1 element")
);
($e:expr) => ({
ValueSetUuid::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetUuid::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_refer {
() => (
compile_error!("ValueSetRefer needs at least 1 element")
);
($e:expr) => ({
ValueSetRefer::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetRefer::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_bool {
() => (
compile_error!("ValueSetBool needs at least 1 element")
);
($e:expr) => ({
ValueSetBool::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetBool::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_syntax {
() => (
compile_error!("ValueSetSyntax needs at least 1 element")
);
($e:expr) => ({
ValueSetSyntax::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetSyntax::new($e);
$(assert!(x.push($item));)*
x
});
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! vs_cid {
() => (
compile_error!("ValueSetCid needs at least 1 element")
);
($e:expr) => ({
ValueSetCid::new($e)
});
($e:expr, $($item:expr),*) => ({
let mut x = ValueSetCid::new($e);
$(assert!(x.push($item));)*
x
});
}
macro_rules! str_concat {
($str_iter:expr, $join_char:expr) => {{
use itertools::Itertools;
$str_iter.iter().join($join_char)
}};
}