use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
use std::fmt;
use std::hash::Hash;
use std::iter;
use std::num::NonZeroU8;
use std::str::FromStr;
use std::sync::Arc;
use concread::arcache::{ARCache, ARCacheReadTxn};
use hashbrown::HashMap;
#[cfg(test)]
use hashbrown::HashSet;
use kanidm_proto::constants::ATTR_UUID;
use kanidm_proto::internal::{Filter as ProtoFilter, OperationError, SchemaError};
use ldap3_proto::proto::{LdapFilter, LdapSubstringFilter};
use serde::Deserialize;
use uuid::Uuid;
use crate::be::{IdxKey, IdxKeyRef, IdxKeyToRef, IdxMeta, IdxSlope};
use crate::idm::ldap::ldap_attr_filter_map;
use crate::prelude::*;
use crate::schema::SchemaTransaction;
use crate::value::{IndexType, PartialValue};
pub type ResolveFilterCache =
ARCache<(IdentityId, Arc<Filter<FilterValid>>), Arc<Filter<FilterValidResolved>>>;
pub type ResolveFilterCacheReadTxn<'a> = ARCacheReadTxn<
'a,
(IdentityId, Arc<Filter<FilterValid>>),
Arc<Filter<FilterValidResolved>>,
(),
>;
pub fn f_eq(a: Attribute, v: PartialValue) -> FC {
FC::Eq(a, v)
}
pub fn f_sub(a: Attribute, v: PartialValue) -> FC {
FC::Cnt(a, v)
}
pub fn f_pres(a: Attribute) -> FC {
FC::Pres(a)
}
pub fn f_lt(a: Attribute, v: PartialValue) -> FC {
FC::LessThan(a, v)
}
pub fn f_or(vs: Vec<FC>) -> FC {
FC::Or(vs)
}
pub fn f_and(vs: Vec<FC>) -> FC {
FC::And(vs)
}
pub fn f_inc(vs: Vec<FC>) -> FC {
FC::Inclusion(vs)
}
pub fn f_andnot(fc: FC) -> FC {
FC::AndNot(Box::new(fc))
}
pub fn f_self() -> FC {
FC::SelfUuid
}
pub fn f_id(uuid: &str) -> FC {
let uf = Uuid::parse_str(uuid)
.ok()
.map(|u| FC::Eq(Attribute::Uuid, PartialValue::Uuid(u)));
let spnf = PartialValue::new_spn_s(uuid).map(|spn| FC::Eq(Attribute::Spn, spn));
let nf = FC::Eq(Attribute::Name, PartialValue::new_iname(uuid));
let f: Vec<_> = iter::once(uf)
.chain(iter::once(spnf))
.flatten()
.chain(iter::once(nf))
.collect();
FC::Or(f)
}
pub fn f_spn_name(id: &str) -> FC {
let spnf = PartialValue::new_spn_s(id).map(|spn| FC::Eq(Attribute::Spn, spn));
let nf = FC::Eq(Attribute::Name, PartialValue::new_iname(id));
let f: Vec<_> = iter::once(spnf).flatten().chain(iter::once(nf)).collect();
FC::Or(f)
}
#[derive(Clone, Debug, Deserialize)]
pub enum FC {
Eq(Attribute, PartialValue),
Cnt(Attribute, PartialValue),
Pres(Attribute),
LessThan(Attribute, PartialValue),
Or(Vec<FC>),
And(Vec<FC>),
Inclusion(Vec<FC>),
AndNot(Box<FC>),
SelfUuid,
}
#[derive(Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
enum FilterComp {
Eq(Attribute, PartialValue),
Cnt(Attribute, PartialValue),
Stw(Attribute, PartialValue),
Enw(Attribute, PartialValue),
Pres(Attribute),
LessThan(Attribute, PartialValue),
Or(Vec<FilterComp>),
And(Vec<FilterComp>),
Inclusion(Vec<FilterComp>),
AndNot(Box<FilterComp>),
SelfUuid,
}
impl fmt::Debug for FilterComp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilterComp::Eq(attr, pv) => {
write!(f, "{} eq {:?}", attr, pv)
}
FilterComp::Cnt(attr, pv) => {
write!(f, "{} cnt {:?}", attr, pv)
}
FilterComp::Stw(attr, pv) => {
write!(f, "{} stw {:?}", attr, pv)
}
FilterComp::Enw(attr, pv) => {
write!(f, "{} enw {:?}", attr, pv)
}
FilterComp::Pres(attr) => {
write!(f, "{} pres", attr)
}
FilterComp::LessThan(attr, pv) => {
write!(f, "{} lt {:?}", attr, pv)
}
FilterComp::And(list) => {
write!(f, "(")?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
write!(f, " and ")?;
}
}
write!(f, ")")
}
FilterComp::Or(list) => {
write!(f, "(")?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
write!(f, " or ")?;
}
}
write!(f, ")")
}
FilterComp::Inclusion(list) => {
write!(f, "(")?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
write!(f, " inc ")?;
}
}
write!(f, ")")
}
FilterComp::AndNot(inner) => {
write!(f, "not ( {:?} )", inner)
}
FilterComp::SelfUuid => {
write!(f, "uuid eq self")
}
}
}
}
#[derive(Clone, Eq)]
pub enum FilterResolved {
Eq(Attribute, PartialValue, Option<NonZeroU8>),
Cnt(Attribute, PartialValue, Option<NonZeroU8>),
Stw(Attribute, PartialValue, Option<NonZeroU8>),
Enw(Attribute, PartialValue, Option<NonZeroU8>),
Pres(Attribute, Option<NonZeroU8>),
LessThan(Attribute, PartialValue, Option<NonZeroU8>),
Or(Vec<FilterResolved>, Option<NonZeroU8>),
And(Vec<FilterResolved>, Option<NonZeroU8>),
Inclusion(Vec<FilterResolved>, Option<NonZeroU8>),
AndNot(Box<FilterResolved>, Option<NonZeroU8>),
}
impl fmt::Debug for FilterResolved {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilterResolved::Eq(attr, pv, idx) => {
write!(
f,
"(s{} {} eq {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::Cnt(attr, pv, idx) => {
write!(
f,
"(s{} {} cnt {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::Stw(attr, pv, idx) => {
write!(
f,
"(s{} {} stw {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::Enw(attr, pv, idx) => {
write!(
f,
"(s{} {} enw {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::Pres(attr, idx) => {
write!(f, "(s{} {} pres)", idx.unwrap_or(NonZeroU8::MAX), attr)
}
FilterResolved::LessThan(attr, pv, idx) => {
write!(
f,
"(s{} {} lt {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::And(list, idx) => {
write!(f, "(s{} ", idx.unwrap_or(NonZeroU8::MAX))?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
write!(f, " and ")?;
}
}
write!(f, ")")
}
FilterResolved::Or(list, idx) => {
write!(f, "(s{} ", idx.unwrap_or(NonZeroU8::MAX))?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
write!(f, " or ")?;
}
}
write!(f, ")")
}
FilterResolved::Inclusion(list, idx) => {
write!(f, "(s{} ", idx.unwrap_or(NonZeroU8::MAX))?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
write!(f, " inc ")?;
}
}
write!(f, ")")
}
FilterResolved::AndNot(inner, idx) => {
write!(f, "not (s{} {:?})", idx.unwrap_or(NonZeroU8::MAX), inner)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilterInvalid {
inner: FilterComp,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct FilterValid {
inner: FilterComp,
}
#[derive(Clone, PartialEq, Eq)]
pub struct FilterValidResolved {
inner: FilterResolved,
}
#[derive(Debug)]
pub enum FilterPlan {
Invalid,
EqIndexed(Attribute, String),
EqUnindexed(Attribute),
EqCorrupt(Attribute),
SubIndexed(Attribute, String),
SubUnindexed(Attribute),
SubCorrupt(Attribute),
PresIndexed(Attribute),
PresUnindexed(Attribute),
PresCorrupt(Attribute),
LessThanUnindexed(Attribute),
OrUnindexed(Vec<FilterPlan>),
OrIndexed(Vec<FilterPlan>),
OrPartial(Vec<FilterPlan>),
OrPartialThreshold(Vec<FilterPlan>),
AndEmptyCand(Vec<FilterPlan>),
AndIndexed(Vec<FilterPlan>),
AndUnindexed(Vec<FilterPlan>),
AndPartial(Vec<FilterPlan>),
AndPartialThreshold(Vec<FilterPlan>),
AndNot(Box<FilterPlan>),
InclusionInvalid(Vec<FilterPlan>),
InclusionIndexed(Vec<FilterPlan>),
}
#[derive(Clone, Hash, Ord, Eq, PartialOrd, PartialEq)]
pub struct Filter<STATE> {
state: STATE,
}
impl fmt::Debug for Filter<FilterValidResolved> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Filter(Valid) {:?}", self.state.inner)
}
}
impl fmt::Debug for Filter<FilterValid> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Filter(Valid) {:?}", self.state.inner)
}
}
impl fmt::Debug for Filter<FilterInvalid> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Filter(Invalid) {:?}", self.state.inner)
}
}
impl Filter<FilterValidResolved> {
#[cfg(test)]
fn optimise(&self) -> Self {
Filter {
state: FilterValidResolved {
inner: self.state.inner.optimise(),
},
}
}
pub fn to_inner(&self) -> &FilterResolved {
&self.state.inner
}
}
impl Filter<FilterValid> {
pub fn invalidate(self) -> Filter<FilterInvalid> {
Filter {
state: FilterInvalid {
inner: self.state.inner,
},
}
}
pub fn resolve(
&self,
ev: &Identity,
idxmeta: Option<&IdxMeta>,
mut rsv_cache: Option<&mut ResolveFilterCacheReadTxn<'_>>,
) -> Result<Filter<FilterValidResolved>, OperationError> {
let cacheable = FilterResolved::resolve_cacheable(&self.state.inner);
let cache_key = if cacheable {
if let Some(rcache) = rsv_cache.as_mut() {
let cache_key = (ev.get_event_origin_id(), Arc::new(self.clone()));
if let Some(f) = rcache.get(&cache_key) {
return Ok(f.as_ref().clone());
};
Some(cache_key)
} else {
None
}
} else {
None
};
let resolved_filt = Filter {
state: FilterValidResolved {
inner: match idxmeta {
Some(idx) => {
FilterResolved::resolve_idx(self.state.inner.clone(), ev, &idx.idxkeys)
}
None => FilterResolved::resolve_no_idx(self.state.inner.clone(), ev),
}
.map(|f| {
match idxmeta {
Some(_) => f.optimise(),
None => f.fast_optimise(),
}
})
.ok_or(OperationError::FilterUuidResolution)?,
},
};
if let Some(cache_key) = cache_key {
if let Some(rcache) = rsv_cache.as_mut() {
rcache.insert(cache_key, Arc::new(resolved_filt.clone()));
}
}
Ok(resolved_filt)
}
pub fn get_attr_set(&self) -> BTreeSet<Attribute> {
let mut r_set = BTreeSet::new();
self.state.inner.get_attr_set(&mut r_set);
r_set
}
pub fn into_ignore_hidden(self) -> Self {
Filter {
state: FilterValid {
inner: FilterComp::new_ignore_hidden(self.state.inner),
},
}
}
pub fn into_recycled(self) -> Self {
Filter {
state: FilterValid {
inner: FilterComp::new_recycled(self.state.inner),
},
}
}
}
impl Filter<FilterInvalid> {
pub fn new(inner: FC) -> Self {
let fc = FilterComp::new(inner);
Filter {
state: FilterInvalid { inner: fc },
}
}
pub fn new_ignore_hidden(inner: FC) -> Self {
let fc = FilterComp::new(inner);
Filter {
state: FilterInvalid {
inner: FilterComp::new_ignore_hidden(fc),
},
}
}
pub fn new_recycled(inner: FC) -> Self {
let fc = FilterComp::new(inner);
Filter {
state: FilterInvalid {
inner: FilterComp::new_recycled(fc),
},
}
}
pub fn join_parts_and(a: Self, b: Self) -> Self {
Filter {
state: FilterInvalid {
inner: FilterComp::And(vec![a.state.inner, b.state.inner]),
},
}
}
#[cfg(test)]
pub fn into_valid_resolved(self) -> Filter<FilterValidResolved> {
let idxmeta = vec![
(Attribute::Uuid, IndexType::Equality),
(Attribute::Uuid, IndexType::Presence),
(Attribute::Name, IndexType::Equality),
(Attribute::Name, IndexType::SubString),
(Attribute::Name, IndexType::Presence),
(Attribute::Class, IndexType::Equality),
(Attribute::Class, IndexType::Presence),
(Attribute::Member, IndexType::Equality),
(Attribute::Member, IndexType::Presence),
(Attribute::MemberOf, IndexType::Equality),
(Attribute::MemberOf, IndexType::Presence),
(Attribute::DirectMemberOf, IndexType::Equality),
(Attribute::DirectMemberOf, IndexType::Presence),
];
let idxmeta_ref = idxmeta.iter().map(|(attr, itype)| (attr, itype)).collect();
Filter {
state: FilterValidResolved {
inner: FilterResolved::from_invalid(self.state.inner, &idxmeta_ref),
},
}
}
#[cfg(test)]
pub fn into_valid(self) -> Filter<FilterValid> {
Filter {
state: FilterValid {
inner: self.state.inner,
},
}
}
pub fn validate(
&self,
schema: &dyn SchemaTransaction,
) -> Result<Filter<FilterValid>, SchemaError> {
Ok(Filter {
state: FilterValid {
inner: self.state.inner.validate(schema)?,
},
})
}
#[instrument(name = "filter::from_ro", level = "trace", skip_all)]
pub fn from_ro(
ev: &Identity,
f: &ProtoFilter,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
let depth = DEFAULT_LIMIT_FILTER_DEPTH_MAX as usize;
let mut elems = ev.limits().filter_max_elements;
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_ro(f, qs, depth, &mut elems)?,
},
})
}
#[instrument(name = "filter::from_rw", level = "trace", skip_all)]
pub fn from_rw(
ev: &Identity,
f: &ProtoFilter,
qs: &mut QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
let depth = DEFAULT_LIMIT_FILTER_DEPTH_MAX as usize;
let mut elems = ev.limits().filter_max_elements;
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_rw(f, qs, depth, &mut elems)?,
},
})
}
#[instrument(name = "filter::from_ldap_ro", level = "trace", skip_all)]
pub fn from_ldap_ro(
ev: &Identity,
f: &LdapFilter,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
let depth = DEFAULT_LIMIT_FILTER_DEPTH_MAX as usize;
let mut elems = ev.limits().filter_max_elements;
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_ldap_ro(f, qs, depth, &mut elems)?,
},
})
}
}
impl FromStr for Filter<FilterInvalid> {
type Err = OperationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f: FC = serde_json::from_str(s).map_err(|_| OperationError::FilterParseError)?;
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::new(f),
},
})
}
}
impl FilterComp {
fn new(fc: FC) -> Self {
match fc {
FC::Eq(a, v) => FilterComp::Eq(a, v),
FC::Cnt(a, v) => FilterComp::Cnt(a, v),
FC::Pres(a) => FilterComp::Pres(a),
FC::LessThan(a, v) => FilterComp::LessThan(a, v),
FC::Or(v) => FilterComp::Or(v.into_iter().map(FilterComp::new).collect()),
FC::And(v) => FilterComp::And(v.into_iter().map(FilterComp::new).collect()),
FC::Inclusion(v) => FilterComp::Inclusion(v.into_iter().map(FilterComp::new).collect()),
FC::AndNot(b) => FilterComp::AndNot(Box::new(FilterComp::new(*b))),
FC::SelfUuid => FilterComp::SelfUuid,
}
}
fn new_ignore_hidden(fc: FilterComp) -> Self {
FilterComp::And(vec![
FilterComp::AndNot(Box::new(FilterComp::Or(vec![
FilterComp::Eq(Attribute::Class, EntryClass::Tombstone.into()),
FilterComp::Eq(Attribute::Class, EntryClass::Recycled.into()),
]))),
fc,
])
}
fn new_recycled(fc: FilterComp) -> Self {
FilterComp::And(vec![
FilterComp::Eq(Attribute::Class, EntryClass::Recycled.into()),
fc,
])
}
fn get_attr_set(&self, r_set: &mut BTreeSet<Attribute>) {
match self {
FilterComp::Eq(attr, _)
| FilterComp::Cnt(attr, _)
| FilterComp::Stw(attr, _)
| FilterComp::Enw(attr, _)
| FilterComp::Pres(attr)
| FilterComp::LessThan(attr, _) => {
r_set.insert(attr.clone());
}
FilterComp::Or(vs) => vs.iter().for_each(|f| f.get_attr_set(r_set)),
FilterComp::And(vs) => vs.iter().for_each(|f| f.get_attr_set(r_set)),
FilterComp::Inclusion(vs) => vs.iter().for_each(|f| f.get_attr_set(r_set)),
FilterComp::AndNot(f) => f.get_attr_set(r_set),
FilterComp::SelfUuid => {
r_set.insert(Attribute::Uuid);
}
}
}
fn validate(&self, schema: &dyn SchemaTransaction) -> Result<FilterComp, SchemaError> {
let schema_attributes = schema.get_attributes();
match self {
FilterComp::Eq(attr, value) => {
match schema_attributes.get(attr) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr, value)
.map(|_| FilterComp::Eq(attr.clone(), value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
}
}
FilterComp::Cnt(attr, value) => {
match schema_attributes.get(attr) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr, value)
.map(|_| FilterComp::Cnt(attr.clone(), value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
}
}
FilterComp::Stw(attr, value) => {
match schema_attributes.get(attr) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr, value)
.map(|_| FilterComp::Stw(attr.clone(), value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
}
}
FilterComp::Enw(attr, value) => {
match schema_attributes.get(attr) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr, value)
.map(|_| FilterComp::Enw(attr.clone(), value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
}
}
FilterComp::Pres(attr) => {
match schema_attributes.get(attr) {
Some(_attr_name) => {
Ok(FilterComp::Pres(attr.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
}
}
FilterComp::LessThan(attr, value) => {
match schema_attributes.get(attr) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr, value)
.map(|_| FilterComp::LessThan(attr.clone(), value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
}
}
FilterComp::Or(filters) => {
if filters.is_empty() {
return Err(SchemaError::EmptyFilter);
};
let x: Result<Vec<_>, _> = filters
.iter()
.map(|filter| filter.validate(schema))
.collect();
x.map(FilterComp::Or)
}
FilterComp::And(filters) => {
if filters.is_empty() {
return Err(SchemaError::EmptyFilter);
};
let x: Result<Vec<_>, _> = filters
.iter()
.map(|filter| filter.validate(schema))
.collect();
x.map(FilterComp::And)
}
FilterComp::Inclusion(filters) => {
if filters.is_empty() {
return Err(SchemaError::EmptyFilter);
};
let x: Result<Vec<_>, _> = filters
.iter()
.map(|filter| filter.validate(schema))
.collect();
x.map(FilterComp::Inclusion)
}
FilterComp::AndNot(filter) => {
filter
.validate(schema)
.map(|r_filter| FilterComp::AndNot(Box::new(r_filter)))
}
FilterComp::SelfUuid => {
Ok(FilterComp::SelfUuid)
}
}
}
fn from_ro(
f: &ProtoFilter,
qs: &mut QueryServerReadTransaction,
depth: usize,
elems: &mut usize,
) -> Result<Self, OperationError> {
let ndepth = depth.checked_sub(1).ok_or(OperationError::ResourceLimit)?;
Ok(match f {
ProtoFilter::Eq(a, v) => {
let nk = Attribute::from(a.as_str());
let v = qs.clone_partialvalue(&nk, v)?;
FilterComp::Eq(nk, v)
}
ProtoFilter::Cnt(a, v) => {
let nk = Attribute::from(a.as_str());
let v = qs.clone_partialvalue(&nk, v)?;
FilterComp::Cnt(nk, v)
}
ProtoFilter::Pres(a) => {
let nk = Attribute::from(a.as_str());
FilterComp::Pres(nk)
}
ProtoFilter::Or(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::Or(
l.iter()
.map(|f| Self::from_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
ProtoFilter::And(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::And(
l.iter()
.map(|f| Self::from_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
ProtoFilter::AndNot(l) => {
*elems = (*elems)
.checked_sub(1)
.ok_or(OperationError::ResourceLimit)?;
FilterComp::AndNot(Box::new(Self::from_ro(l, qs, ndepth, elems)?))
}
ProtoFilter::SelfUuid => FilterComp::SelfUuid,
})
}
fn from_rw(
f: &ProtoFilter,
qs: &mut QueryServerWriteTransaction,
depth: usize,
elems: &mut usize,
) -> Result<Self, OperationError> {
let ndepth = depth.checked_sub(1).ok_or(OperationError::ResourceLimit)?;
Ok(match f {
ProtoFilter::Eq(a, v) => {
let nk = Attribute::from(a.as_str());
let v = qs.clone_partialvalue(&nk, v)?;
FilterComp::Eq(nk, v)
}
ProtoFilter::Cnt(a, v) => {
let nk = Attribute::from(a.as_str());
let v = qs.clone_partialvalue(&nk, v)?;
FilterComp::Cnt(nk, v)
}
ProtoFilter::Pres(a) => {
let nk = Attribute::from(a.as_str());
FilterComp::Pres(nk)
}
ProtoFilter::Or(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::Or(
l.iter()
.map(|f| Self::from_rw(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
ProtoFilter::And(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::And(
l.iter()
.map(|f| Self::from_rw(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
ProtoFilter::AndNot(l) => {
*elems = (*elems)
.checked_sub(1)
.ok_or(OperationError::ResourceLimit)?;
FilterComp::AndNot(Box::new(Self::from_rw(l, qs, ndepth, elems)?))
}
ProtoFilter::SelfUuid => FilterComp::SelfUuid,
})
}
fn from_ldap_ro(
f: &LdapFilter,
qs: &mut QueryServerReadTransaction,
depth: usize,
elems: &mut usize,
) -> Result<Self, OperationError> {
let ndepth = depth.checked_sub(1).ok_or(OperationError::ResourceLimit)?;
Ok(match f {
LdapFilter::And(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::And(
l.iter()
.map(|f| Self::from_ldap_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
LdapFilter::Or(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::Or(
l.iter()
.map(|f| Self::from_ldap_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
LdapFilter::Not(l) => {
*elems = (*elems)
.checked_sub(1)
.ok_or(OperationError::ResourceLimit)?;
FilterComp::AndNot(Box::new(Self::from_ldap_ro(l, qs, ndepth, elems)?))
}
LdapFilter::Equality(a, v) => {
let a = ldap_attr_filter_map(a);
let v = qs.clone_partialvalue(&a, v)?;
FilterComp::Eq(a, v)
}
LdapFilter::Present(a) => FilterComp::Pres(ldap_attr_filter_map(a)),
LdapFilter::Substring(
a,
LdapSubstringFilter {
initial,
any,
final_,
},
) => {
let a = ldap_attr_filter_map(a);
let mut terms = Vec::with_capacity(any.len() + 2);
if let Some(ini) = initial {
let v = qs.clone_partialvalue(&a, ini)?;
terms.push(FilterComp::Stw(a.clone(), v));
}
for term in any.iter() {
let v = qs.clone_partialvalue(&a, term)?;
terms.push(FilterComp::Cnt(a.clone(), v));
}
if let Some(fin) = final_ {
let v = qs.clone_partialvalue(&a, fin)?;
terms.push(FilterComp::Enw(a.clone(), v));
}
FilterComp::And(terms)
}
LdapFilter::GreaterOrEqual(_, _) => {
admin_error!("Unsupported filter operation - greater or equal");
return Err(OperationError::FilterGeneration);
}
LdapFilter::LessOrEqual(_, _) => {
admin_error!("Unsupported filter operation - less or equal");
return Err(OperationError::FilterGeneration);
}
LdapFilter::Approx(_, _) => {
admin_error!("Unsupported filter operation - approximate");
return Err(OperationError::FilterGeneration);
}
LdapFilter::Extensible(_) => {
admin_error!("Unsupported filter operation - extensible");
return Err(OperationError::FilterGeneration);
}
})
}
}
#[cfg(test)]
impl PartialOrd for Filter<FilterValidResolved> {
fn partial_cmp(&self, rhs: &Filter<FilterValidResolved>) -> Option<Ordering> {
self.state.inner.partial_cmp(&rhs.state.inner)
}
}
impl PartialEq for FilterResolved {
fn eq(&self, rhs: &FilterResolved) -> bool {
match (self, rhs) {
(FilterResolved::Eq(a1, v1, _), FilterResolved::Eq(a2, v2, _)) => a1 == a2 && v1 == v2,
(FilterResolved::Cnt(a1, v1, _), FilterResolved::Cnt(a2, v2, _)) => {
a1 == a2 && v1 == v2
}
(FilterResolved::Pres(a1, _), FilterResolved::Pres(a2, _)) => a1 == a2,
(FilterResolved::LessThan(a1, v1, _), FilterResolved::LessThan(a2, v2, _)) => {
a1 == a2 && v1 == v2
}
(FilterResolved::And(vs1, _), FilterResolved::And(vs2, _)) => vs1 == vs2,
(FilterResolved::Or(vs1, _), FilterResolved::Or(vs2, _)) => vs1 == vs2,
(FilterResolved::Inclusion(vs1, _), FilterResolved::Inclusion(vs2, _)) => vs1 == vs2,
(FilterResolved::AndNot(f1, _), FilterResolved::AndNot(f2, _)) => f1 == f2,
(_, _) => false,
}
}
}
impl PartialOrd for FilterResolved {
fn partial_cmp(&self, rhs: &FilterResolved) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
impl Ord for FilterResolved {
fn cmp(&self, rhs: &FilterResolved) -> Ordering {
let left_slopey = self.get_slopeyness_factor();
let right_slopey = rhs.get_slopeyness_factor();
let r = match (left_slopey, right_slopey) {
(Some(sfl), Some(sfr)) => sfl.cmp(&sfr),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => Ordering::Equal,
};
if r == Ordering::Equal {
match (self, rhs) {
(FilterResolved::Eq(a1, v1, _), FilterResolved::Eq(a2, v2, _))
| (FilterResolved::Cnt(a1, v1, _), FilterResolved::Cnt(a2, v2, _))
| (FilterResolved::LessThan(a1, v1, _), FilterResolved::LessThan(a2, v2, _)) => {
match a1.cmp(a2) {
Ordering::Equal => v1.cmp(v2),
o => o,
}
}
(FilterResolved::Pres(a1, _), FilterResolved::Pres(a2, _)) => a1.cmp(a2),
(FilterResolved::Eq(_, _, _), _) => Ordering::Less,
(_, FilterResolved::Eq(_, _, _)) => Ordering::Greater,
(FilterResolved::Pres(_, _), _) => Ordering::Less,
(_, FilterResolved::Pres(_, _)) => Ordering::Greater,
(FilterResolved::LessThan(_, _, _), _) => Ordering::Less,
(_, FilterResolved::LessThan(_, _, _)) => Ordering::Greater,
(FilterResolved::Cnt(_, _, _), _) => Ordering::Less,
(_, FilterResolved::Cnt(_, _, _)) => Ordering::Greater,
(_, _) => Ordering::Equal,
}
} else {
r
}
}
}
impl FilterResolved {
#[cfg(test)]
fn from_invalid(fc: FilterComp, idxmeta: &HashSet<(&Attribute, &IndexType)>) -> Self {
match fc {
FilterComp::Eq(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::Equality));
let idx = NonZeroU8::new(idx as u8);
FilterResolved::Eq(a, v, idx)
}
FilterComp::SelfUuid => panic!("Not possible to resolve SelfUuid in from_invalid!"),
FilterComp::Cnt(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::SubString));
let idx = NonZeroU8::new(idx as u8);
FilterResolved::Cnt(a, v, idx)
}
FilterComp::Stw(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::SubString));
let idx = NonZeroU8::new(idx as u8);
FilterResolved::Stw(a, v, idx)
}
FilterComp::Enw(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::SubString));
let idx = NonZeroU8::new(idx as u8);
FilterResolved::Enw(a, v, idx)
}
FilterComp::Pres(a) => {
let idx = idxmeta.contains(&(&a, &IndexType::Presence));
FilterResolved::Pres(a, NonZeroU8::new(idx as u8))
}
FilterComp::LessThan(a, v) => {
FilterResolved::LessThan(a, v, None)
}
FilterComp::Or(vs) => FilterResolved::Or(
vs.into_iter()
.map(|v| FilterResolved::from_invalid(v, idxmeta))
.collect(),
None,
),
FilterComp::And(vs) => FilterResolved::And(
vs.into_iter()
.map(|v| FilterResolved::from_invalid(v, idxmeta))
.collect(),
None,
),
FilterComp::Inclusion(vs) => FilterResolved::Inclusion(
vs.into_iter()
.map(|v| FilterResolved::from_invalid(v, idxmeta))
.collect(),
None,
),
FilterComp::AndNot(f) => {
FilterResolved::AndNot(
Box::new(FilterResolved::from_invalid((*f).clone(), idxmeta)),
None,
)
}
}
}
fn resolve_cacheable(fc: &FilterComp) -> bool {
match fc {
FilterComp::Or(vs) | FilterComp::And(vs) | FilterComp::Inclusion(vs) => {
if vs.len() < 8 {
vs.iter().all(FilterResolved::resolve_cacheable)
} else {
false
}
}
FilterComp::AndNot(f) => FilterResolved::resolve_cacheable(f.as_ref()),
FilterComp::Eq(..)
| FilterComp::SelfUuid
| FilterComp::Cnt(..)
| FilterComp::Stw(..)
| FilterComp::Enw(..)
| FilterComp::Pres(_)
| FilterComp::LessThan(..) => true,
}
}
fn resolve_idx(
fc: FilterComp,
ev: &Identity,
idxmeta: &HashMap<IdxKey, IdxSlope>,
) -> Option<Self> {
match fc {
FilterComp::Eq(a, v) => {
let idxkref = IdxKeyRef::new(&a, &IndexType::Equality);
let idx = idxmeta
.get(&idxkref as &dyn IdxKeyToRef)
.copied()
.and_then(NonZeroU8::new);
Some(FilterResolved::Eq(a, v, idx))
}
FilterComp::SelfUuid => ev.get_uuid().map(|uuid| {
let idxkref = IdxKeyRef::new(Attribute::Uuid.as_ref(), &IndexType::Equality);
let idx = idxmeta
.get(&idxkref as &dyn IdxKeyToRef)
.copied()
.and_then(NonZeroU8::new);
FilterResolved::Eq(Attribute::Uuid, PartialValue::Uuid(uuid), idx)
}),
FilterComp::Cnt(a, v) => {
let idxkref = IdxKeyRef::new(&a, &IndexType::SubString);
let idx = idxmeta
.get(&idxkref as &dyn IdxKeyToRef)
.copied()
.and_then(NonZeroU8::new);
Some(FilterResolved::Cnt(a, v, idx))
}
FilterComp::Stw(a, v) => {
let idxkref = IdxKeyRef::new(&a, &IndexType::SubString);
let idx = idxmeta
.get(&idxkref as &dyn IdxKeyToRef)
.copied()
.and_then(NonZeroU8::new);
Some(FilterResolved::Stw(a, v, idx))
}
FilterComp::Enw(a, v) => {
let idxkref = IdxKeyRef::new(&a, &IndexType::SubString);
let idx = idxmeta
.get(&idxkref as &dyn IdxKeyToRef)
.copied()
.and_then(NonZeroU8::new);
Some(FilterResolved::Enw(a, v, idx))
}
FilterComp::Pres(a) => {
let idxkref = IdxKeyRef::new(&a, &IndexType::Presence);
let idx = idxmeta
.get(&idxkref as &dyn IdxKeyToRef)
.copied()
.and_then(NonZeroU8::new);
Some(FilterResolved::Pres(a, idx))
}
FilterComp::LessThan(a, v) => {
Some(FilterResolved::LessThan(a, v, None))
}
FilterComp::Or(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_idx(f, ev, idxmeta))
.collect();
fi.map(|fi| FilterResolved::Or(fi, None))
}
FilterComp::And(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_idx(f, ev, idxmeta))
.collect();
fi.map(|fi| FilterResolved::And(fi, None))
}
FilterComp::Inclusion(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_idx(f, ev, idxmeta))
.collect();
fi.map(|fi| FilterResolved::Inclusion(fi, None))
}
FilterComp::AndNot(f) => {
FilterResolved::resolve_idx((*f).clone(), ev, idxmeta)
.map(|fi| FilterResolved::AndNot(Box::new(fi), None))
}
}
}
fn resolve_no_idx(fc: FilterComp, ev: &Identity) -> Option<Self> {
match fc {
FilterComp::Eq(a, v) => {
let idx = matches!(a.as_str(), ATTR_NAME | ATTR_UUID);
let idx = NonZeroU8::new(idx as u8);
Some(FilterResolved::Eq(a, v, idx))
}
FilterComp::SelfUuid => ev.get_uuid().map(|uuid| {
FilterResolved::Eq(
Attribute::Uuid,
PartialValue::Uuid(uuid),
NonZeroU8::new(true as u8),
)
}),
FilterComp::Cnt(a, v) => Some(FilterResolved::Cnt(a, v, None)),
FilterComp::Stw(a, v) => Some(FilterResolved::Stw(a, v, None)),
FilterComp::Enw(a, v) => Some(FilterResolved::Enw(a, v, None)),
FilterComp::Pres(a) => Some(FilterResolved::Pres(a, None)),
FilterComp::LessThan(a, v) => Some(FilterResolved::LessThan(a, v, None)),
FilterComp::Or(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_no_idx(f, ev))
.collect();
fi.map(|fi| FilterResolved::Or(fi, None))
}
FilterComp::And(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_no_idx(f, ev))
.collect();
fi.map(|fi| FilterResolved::And(fi, None))
}
FilterComp::Inclusion(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_no_idx(f, ev))
.collect();
fi.map(|fi| FilterResolved::Inclusion(fi, None))
}
FilterComp::AndNot(f) => {
FilterResolved::resolve_no_idx((*f).clone(), ev)
.map(|fi| FilterResolved::AndNot(Box::new(fi), None))
}
}
}
fn fast_optimise(self) -> Self {
match self {
FilterResolved::Inclusion(mut f_list, _) => {
f_list.sort_unstable();
f_list.dedup();
let sf = f_list.last().and_then(|f| f.get_slopeyness_factor());
FilterResolved::Inclusion(f_list, sf)
}
FilterResolved::And(mut f_list, _) => {
f_list.sort_unstable();
f_list.dedup();
let sf = f_list.first().and_then(|f| f.get_slopeyness_factor());
FilterResolved::And(f_list, sf)
}
v => v,
}
}
fn optimise(&self) -> Self {
match self {
FilterResolved::Inclusion(f_list, _) => {
let (f_list_inc, mut f_list_new): (Vec<_>, Vec<_>) = f_list
.iter()
.map(|f_ref| f_ref.optimise())
.partition(|f| matches!(f, FilterResolved::Inclusion(_, _)));
f_list_inc.into_iter().for_each(|fc| {
if let FilterResolved::Inclusion(mut l, _) = fc {
f_list_new.append(&mut l)
}
});
f_list_new.sort_unstable();
f_list_new.dedup();
let sf = f_list_new.last().and_then(|f| f.get_slopeyness_factor());
FilterResolved::Inclusion(f_list_new, sf)
}
FilterResolved::And(f_list, _) => {
let (f_list_and, mut f_list_new): (Vec<_>, Vec<_>) = f_list
.iter()
.map(|f_ref| f_ref.optimise())
.partition(|f| matches!(f, FilterResolved::And(_, _)));
f_list_and.into_iter().for_each(|fc| {
if let FilterResolved::And(mut l, _) = fc {
f_list_new.append(&mut l)
}
});
if f_list_new.len() == 1 {
f_list_new.remove(0)
} else {
f_list_new.sort_unstable();
f_list_new.dedup();
let sf = f_list_new.first().and_then(|f| f.get_slopeyness_factor());
FilterResolved::And(f_list_new, sf)
}
}
FilterResolved::Or(f_list, _) => {
let (f_list_or, mut f_list_new): (Vec<_>, Vec<_>) = f_list
.iter()
.map(|f_ref| f_ref.optimise())
.partition(|f| matches!(f, FilterResolved::Or(_, _)));
f_list_or.into_iter().for_each(|fc| {
if let FilterResolved::Or(mut l, _) = fc {
f_list_new.append(&mut l)
}
});
if f_list_new.len() == 1 {
f_list_new.remove(0)
} else {
#[allow(clippy::unnecessary_sort_by)]
f_list_new.sort_unstable_by(|a, b| b.cmp(a));
f_list_new.dedup();
let sf = f_list_new.last().and_then(|f| f.get_slopeyness_factor());
FilterResolved::Or(f_list_new, sf)
}
}
f => f.clone(),
}
}
pub fn is_andnot(&self) -> bool {
matches!(self, FilterResolved::AndNot(_, _))
}
#[inline(always)]
fn get_slopeyness_factor(&self) -> Option<NonZeroU8> {
match self {
FilterResolved::Eq(_, _, sf)
| FilterResolved::Cnt(_, _, sf)
| FilterResolved::Stw(_, _, sf)
| FilterResolved::Enw(_, _, sf)
| FilterResolved::Pres(_, sf)
| FilterResolved::LessThan(_, _, sf)
| FilterResolved::Or(_, sf)
| FilterResolved::And(_, sf)
| FilterResolved::Inclusion(_, sf)
| FilterResolved::AndNot(_, sf) => *sf,
}
}
}
#[cfg(test)]
mod tests {
use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
use std::time::Duration;
use kanidm_proto::internal::Filter as ProtoFilter;
use ldap3_proto::simple::LdapFilter;
use crate::event::{CreateEvent, DeleteEvent};
use crate::filter::{Filter, FilterInvalid, DEFAULT_LIMIT_FILTER_DEPTH_MAX};
use crate::prelude::*;
#[test]
fn test_filter_simple() {
let _filt: Filter<FilterInvalid> = filter!(f_eq(Attribute::Class, EntryClass::User.into()));
let _complex_filt: Filter<FilterInvalid> = filter!(f_and!([
f_or!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("test_a")),
f_eq(Attribute::UserId, PartialValue::new_iutf8("test_b")),
]),
f_sub(Attribute::Class, EntryClass::User.into()),
]));
}
macro_rules! filter_optimise_assert {
(
$init:expr,
$expect:expr
) => {{
#[allow(unused_imports)]
use crate::filter::{f_and, f_andnot, f_eq, f_or, f_pres, f_sub};
use crate::filter::{Filter, FilterInvalid};
let f_init: Filter<FilterInvalid> = Filter::new($init);
let f_expect: Filter<FilterInvalid> = Filter::new($expect);
let f_init_r = f_init.into_valid_resolved();
let f_init_o = f_init_r.optimise();
let f_init_e = f_expect.into_valid_resolved();
debug!("--");
debug!("init --> {:?}", f_init_r);
debug!("opt --> {:?}", f_init_o);
debug!("expect --> {:?}", f_init_e);
assert_eq!(f_init_o, f_init_e);
}};
}
#[test]
fn test_filter_optimise() {
sketching::test_init();
filter_optimise_assert!(
f_and(vec![f_and(vec![f_eq(
Attribute::Class,
EntryClass::TestClass.into()
)])]),
f_eq(Attribute::Class, EntryClass::TestClass.into())
);
filter_optimise_assert!(
f_or(vec![f_or(vec![f_eq(
Attribute::Class,
EntryClass::TestClass.into()
)])]),
f_eq(Attribute::Class, EntryClass::TestClass.into())
);
filter_optimise_assert!(
f_and(vec![f_or(vec![f_and(vec![f_eq(
Attribute::Class,
EntryClass::TestClass.to_partialvalue()
)])])]),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue())
);
filter_optimise_assert!(
f_and(vec![
f_and(vec![f_eq(
Attribute::Class,
EntryClass::TestClass.to_partialvalue()
)]),
f_sub(Attribute::Class, PartialValue::new_class("te")),
f_pres(Attribute::Class),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue())
]),
f_and(vec![
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_pres(Attribute::Class),
f_sub(Attribute::Class, PartialValue::new_class("te")),
])
);
filter_optimise_assert!(
f_and(vec![
f_and(vec![
f_eq(Attribute::Class, PartialValue::new_class("foo")),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_eq(Attribute::Uid, PartialValue::new_class("bar")),
]),
f_sub(Attribute::Class, PartialValue::new_class("te")),
f_pres(Attribute::Class),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue())
]),
f_and(vec![
f_eq(Attribute::Class, PartialValue::new_class("foo")),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_pres(Attribute::Class),
f_eq(Attribute::Uid, PartialValue::new_class("bar")),
f_sub(Attribute::Class, PartialValue::new_class("te")),
])
);
filter_optimise_assert!(
f_or(vec![
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_pres(Attribute::Class),
f_sub(Attribute::Class, PartialValue::new_class("te")),
f_or(vec![f_eq(
Attribute::Class,
EntryClass::TestClass.to_partialvalue()
)]),
]),
f_or(vec![
f_sub(Attribute::Class, PartialValue::new_class("te")),
f_pres(Attribute::Class),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue())
])
);
filter_optimise_assert!(
f_or(vec![
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_and(vec![
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_eq(Attribute::Term, EntryClass::TestClass.to_partialvalue()),
f_or(vec![f_eq(
Attribute::Class,
EntryClass::TestClass.to_partialvalue()
)])
]),
]),
f_or(vec![
f_and(vec![
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
f_eq(Attribute::Term, EntryClass::TestClass.to_partialvalue())
]),
f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()),
])
);
}
#[test]
fn test_filter_eq() {
let f_t1a = filter!(f_pres(Attribute::UserId));
let f_t1b = filter!(f_pres(Attribute::UserId));
let f_t1c = filter!(f_pres(Attribute::NonExist));
assert_eq!(f_t1a, f_t1b);
assert!(f_t1a != f_t1c);
assert!(f_t1b != f_t1c);
let f_t2a = filter!(f_and!([f_pres(Attribute::UserId)]));
let f_t2b = filter!(f_and!([f_pres(Attribute::UserId)]));
let f_t2c = filter!(f_and!([f_pres(Attribute::NonExist)]));
assert_eq!(f_t2a, f_t2b);
assert!(f_t2a != f_t2c);
assert!(f_t2b != f_t2c);
assert!(f_t2c != f_t1a);
assert!(f_t2c != f_t1c);
}
#[test]
fn test_filter_ord() {
let f_t1a = filter_resolved!(f_pres(Attribute::UserId));
let f_t1b = filter_resolved!(f_pres(Attribute::UserId));
assert_eq!(f_t1a.partial_cmp(&f_t1b), Some(Ordering::Equal));
assert_eq!(f_t1b.partial_cmp(&f_t1a), Some(Ordering::Equal));
let f_t2a = filter_resolved!(f_and!([]));
let f_t2b = filter_resolved!(f_and!([]));
assert_eq!(f_t2a.partial_cmp(&f_t2b), Some(Ordering::Equal));
assert_eq!(f_t2b.partial_cmp(&f_t2a), Some(Ordering::Equal));
let f_t3b = filter_resolved!(f_eq(Attribute::UserId, PartialValue::new_iutf8("")));
assert_eq!(f_t1a.partial_cmp(&f_t3b), Some(Ordering::Greater));
assert_eq!(f_t3b.partial_cmp(&f_t1a), Some(Ordering::Less));
let f_t4b = filter_resolved!(f_sub(Attribute::UserId, PartialValue::new_iutf8("")));
assert_eq!(f_t1a.partial_cmp(&f_t4b), Some(Ordering::Less));
assert_eq!(f_t3b.partial_cmp(&f_t4b), Some(Ordering::Less));
assert_eq!(f_t4b.partial_cmp(&f_t1a), Some(Ordering::Greater));
assert_eq!(f_t4b.partial_cmp(&f_t3b), Some(Ordering::Greater));
}
#[test]
fn test_filter_clone() {
let f_t1a = filter_resolved!(f_pres(Attribute::UserId));
let f_t1b = f_t1a.clone();
let f_t1c = filter_resolved!(f_pres(Attribute::NonExist));
assert_eq!(f_t1a, f_t1b);
assert!(f_t1a != f_t1c);
let f_t2a = filter_resolved!(f_and!([f_pres(Attribute::UserId)]));
let f_t2b = f_t2a.clone();
let f_t2c = filter_resolved!(f_and!([f_pres(Attribute::NonExist)]));
assert_eq!(f_t2a, f_t2b);
assert!(f_t2a != f_t2c);
}
#[test]
fn test_lessthan_entry_filter() {
let e = entry_init!(
(Attribute::UserId, Value::new_iutf8("william")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
),
(Attribute::GidNumber, Value::Uint32(1000))
)
.into_sealed_new();
let f_t1a = filter_resolved!(f_lt(Attribute::GidNumber, PartialValue::new_uint32(500)));
assert!(!e.entry_match_no_index(&f_t1a));
let f_t1b = filter_resolved!(f_lt(Attribute::GidNumber, PartialValue::new_uint32(1000)));
assert!(!e.entry_match_no_index(&f_t1b));
let f_t1c = filter_resolved!(f_lt(Attribute::GidNumber, PartialValue::new_uint32(1001)));
assert!(e.entry_match_no_index(&f_t1c));
}
#[test]
fn test_or_entry_filter() {
let e = entry_init!(
(Attribute::UserId, Value::new_iutf8("william")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
),
(Attribute::GidNumber, Value::Uint32(1000))
)
.into_sealed_new();
let f_t1a = filter_resolved!(f_or!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("william")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1000)),
]));
assert!(e.entry_match_no_index(&f_t1a));
let f_t2a = filter_resolved!(f_or!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("william")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1000)),
]));
assert!(e.entry_match_no_index(&f_t2a));
let f_t3a = filter_resolved!(f_or!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("alice")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1000)),
]));
assert!(e.entry_match_no_index(&f_t3a));
let f_t4a = filter_resolved!(f_or!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("alice")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1001)),
]));
assert!(!e.entry_match_no_index(&f_t4a));
}
#[test]
fn test_and_entry_filter() {
let e = entry_init!(
(Attribute::UserId, Value::new_iutf8("william")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
),
(Attribute::GidNumber, Value::Uint32(1000))
)
.into_sealed_new();
let f_t1a = filter_resolved!(f_and!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("william")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1000)),
]));
assert!(e.entry_match_no_index(&f_t1a));
let f_t2a = filter_resolved!(f_and!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("william")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1001)),
]));
assert!(!e.entry_match_no_index(&f_t2a));
let f_t3a = filter_resolved!(f_and!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("alice")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1000)),
]));
assert!(!e.entry_match_no_index(&f_t3a));
let f_t4a = filter_resolved!(f_and!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("alice")),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1001)),
]));
assert!(!e.entry_match_no_index(&f_t4a));
}
#[test]
fn test_not_entry_filter() {
let e1 = entry_init!(
(Attribute::UserId, Value::new_iutf8("william")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
),
(Attribute::GidNumber, Value::Uint32(1000))
)
.into_sealed_new();
let f_t1a = filter_resolved!(f_andnot(f_eq(
Attribute::UserId,
PartialValue::new_iutf8("alice")
)));
assert!(e1.entry_match_no_index(&f_t1a));
let f_t2a = filter_resolved!(f_andnot(f_eq(
Attribute::UserId,
PartialValue::new_iutf8("william")
)));
assert!(!e1.entry_match_no_index(&f_t2a));
}
#[test]
fn test_nested_entry_filter() {
let e1 = entry_init!(
(Attribute::Class, EntryClass::Person.to_value().clone()),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
),
(Attribute::GidNumber, Value::Uint32(1000))
)
.into_sealed_new();
let e2 = entry_init!(
(Attribute::Class, EntryClass::Person.to_value().clone()),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("4b6228ab-1dbe-42a4-a9f5-f6368222438e"))
),
(Attribute::GidNumber, Value::Uint32(1001))
)
.into_sealed_new();
let e3 = entry_init!(
(Attribute::Class, EntryClass::Person.to_value()),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("7b23c99d-c06b-4a9a-a958-3afa56383e1d"))
),
(Attribute::GidNumber, Value::Uint32(1002))
)
.into_sealed_new();
let e4 = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("21d816b5-1f6a-4696-b7c1-6ed06d22ed81"))
),
(Attribute::GidNumber, Value::Uint32(1000))
)
.into_sealed_new();
let f_t1a = filter_resolved!(f_and!([
f_eq(Attribute::Class, EntryClass::Person.into()),
f_or!([
f_eq(Attribute::GidNumber, PartialValue::Uint32(1001)),
f_eq(Attribute::GidNumber, PartialValue::Uint32(1000))
])
]));
assert!(e1.entry_match_no_index(&f_t1a));
assert!(e2.entry_match_no_index(&f_t1a));
assert!(!e3.entry_match_no_index(&f_t1a));
assert!(!e4.entry_match_no_index(&f_t1a));
}
#[test]
fn test_attr_set_filter() {
let mut f_expect = BTreeSet::new();
f_expect.insert(Attribute::from("userid"));
f_expect.insert(Attribute::Class);
let f_t1a = filter_valid!(f_and!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("alice")),
f_eq(Attribute::Class, PartialValue::new_iutf8("1001")),
]));
assert_eq!(f_t1a.get_attr_set(), f_expect);
let f_t2a = filter_valid!(f_and!([
f_eq(Attribute::UserId, PartialValue::new_iutf8("alice")),
f_eq(Attribute::Class, PartialValue::new_iutf8("1001")),
f_eq(Attribute::UserId, PartialValue::new_iutf8("claire")),
]));
assert_eq!(f_t2a.get_attr_set(), f_expect);
}
#[qs_test]
async fn test_filter_resolve_value(server: &QueryServer) {
let time_p1 = duration_from_epoch_now();
let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
let time_p3 = time_p2 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
let mut server_txn = server.write(time_p1).await.expect("txn");
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let e2 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson2")),
(
Attribute::Uuid,
Value::Uuid(uuid::uuid!("a67c0c71-0b35-4218-a6b0-22d23d131d27"))
),
(Attribute::Description, Value::new_utf8s("testperson2")),
(Attribute::DisplayName, Value::new_utf8s("testperson2"))
);
let e_ts = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson3")),
(
Attribute::Uuid,
Value::Uuid(uuid!("9557f49c-97a5-4277-a9a5-097d17eb8317"))
),
(Attribute::Description, Value::new_utf8s("testperson3")),
(Attribute::DisplayName, Value::new_utf8s("testperson3"))
);
let ce = CreateEvent::new_internal(vec![e1, e2, e_ts]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let de_sin = DeleteEvent::new_internal_invalid(filter!(f_or!([f_eq(
Attribute::Name,
PartialValue::new_iname("testperson3")
)])));
assert!(server_txn.delete(&de_sin).is_ok());
assert!(server_txn.commit().is_ok());
let mut server_txn = server.write(time_p2).await.expect("txn");
assert!(server_txn.purge_recycled().is_ok());
assert!(server_txn.commit().is_ok());
let mut server_txn = server.write(time_p3).await.expect("txn");
assert!(server_txn.purge_tombstones().is_ok());
let t1 = vs_utf8!["teststring".to_string()] as _;
let r1 = server_txn.resolve_valueset(&t1);
assert_eq!(r1, Ok(vec!["teststring".to_string()]));
let t_uuid = vs_refer![uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")] as _;
let r_uuid = server_txn.resolve_valueset(&t_uuid);
debug!("{:?}", r_uuid);
assert_eq!(r_uuid, Ok(vec!["testperson1@example.com".to_string()]));
let t_uuid = vs_refer![uuid!("a67c0c71-0b35-4218-a6b0-22d23d131d27")] as _;
let r_uuid = server_txn.resolve_valueset(&t_uuid);
debug!("{:?}", r_uuid);
assert_eq!(r_uuid, Ok(vec!["testperson2@example.com".to_string()]));
let t_uuid_non = vs_refer![uuid!("b83e98f0-3d2e-41d2-9796-d8d993289c86")] as _;
let r_uuid_non = server_txn.resolve_valueset(&t_uuid_non);
debug!("{:?}", r_uuid_non);
assert_eq!(
r_uuid_non,
Ok(vec!["b83e98f0-3d2e-41d2-9796-d8d993289c86".to_string()])
);
let t_uuid_ts = vs_refer![uuid!("9557f49c-97a5-4277-a9a5-097d17eb8317")] as _;
let r_uuid_ts = server_txn.resolve_valueset(&t_uuid_ts);
debug!("{:?}", r_uuid_ts);
assert_eq!(
r_uuid_ts,
Ok(vec!["9557f49c-97a5-4277-a9a5-097d17eb8317".to_string()])
);
}
#[qs_test]
async fn test_filter_depth_limits(server: &QueryServer) {
let mut r_txn = server.read().await.unwrap();
let mut inv_proto = ProtoFilter::Pres(Attribute::Class.to_string());
for _i in 0..(DEFAULT_LIMIT_FILTER_DEPTH_MAX + 1) {
inv_proto = ProtoFilter::And(vec![inv_proto]);
}
let mut inv_ldap = LdapFilter::Present(Attribute::Class.to_string());
for _i in 0..(DEFAULT_LIMIT_FILTER_DEPTH_MAX + 1) {
inv_ldap = LdapFilter::And(vec![inv_ldap]);
}
let ev = Identity::from_internal();
let res = Filter::from_ro(&ev, &inv_proto, &mut r_txn);
assert_eq!(res, Err(OperationError::ResourceLimit));
let res = Filter::from_ldap_ro(&ev, &inv_ldap, &mut r_txn);
assert_eq!(res, Err(OperationError::ResourceLimit));
std::mem::drop(r_txn);
let mut wr_txn = server.write(duration_from_epoch_now()).await.expect("txn");
let res = Filter::from_rw(&ev, &inv_proto, &mut wr_txn);
assert_eq!(res, Err(OperationError::ResourceLimit));
}
#[qs_test]
async fn test_filter_max_element_limits(server: &QueryServer) {
const LIMIT: usize = 4;
let mut r_txn = server.read().await.unwrap();
let inv_proto = ProtoFilter::And(
(0..(LIMIT * 2))
.map(|_| ProtoFilter::Pres(Attribute::Class.to_string()))
.collect(),
);
let inv_ldap = LdapFilter::And(
(0..(LIMIT * 2))
.map(|_| LdapFilter::Present(Attribute::Class.to_string()))
.collect(),
);
let mut ev = Identity::from_internal();
ev.limits_mut().filter_max_elements = LIMIT;
let res = Filter::from_ro(&ev, &inv_proto, &mut r_txn);
assert_eq!(res, Err(OperationError::ResourceLimit));
let res = Filter::from_ldap_ro(&ev, &inv_ldap, &mut r_txn);
assert_eq!(res, Err(OperationError::ResourceLimit));
std::mem::drop(r_txn);
let mut wr_txn = server.write(duration_from_epoch_now()).await.expect("txn");
let res = Filter::from_rw(&ev, &inv_proto, &mut wr_txn);
assert_eq!(res, Err(OperationError::ResourceLimit));
}
}