use crate::audit::AuditScope;
use crate::be::IdxKey;
use crate::event::{Event, EventOrigin};
use crate::ldap::ldap_attr_filter_map;
use crate::schema::SchemaTransaction;
use crate::server::{
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
};
use crate::value::{IndexType, PartialValue};
use hashbrown::HashSet;
use kanidm_proto::v1::Filter as ProtoFilter;
use kanidm_proto::v1::{OperationError, SchemaError};
use ldap3_server::simple::LdapFilter;
use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
use std::iter;
use uuid::Uuid;
#[allow(dead_code)]
pub fn f_eq(a: &str, v: PartialValue) -> FC {
FC::Eq(a, v)
}
#[allow(dead_code)]
pub fn f_sub(a: &str, v: PartialValue) -> FC {
FC::Sub(a, v)
}
#[allow(dead_code)]
pub fn f_pres(a: &str) -> FC {
FC::Pres(a)
}
#[allow(dead_code)]
pub fn f_lt(a: &str, v: PartialValue) -> FC {
FC::LessThan(a, v)
}
#[allow(dead_code)]
pub fn f_or(vs: Vec<FC>) -> FC {
FC::Or(vs)
}
#[allow(dead_code)]
pub fn f_and(vs: Vec<FC>) -> FC {
FC::And(vs)
}
#[allow(dead_code)]
pub fn f_inc(vs: Vec<FC>) -> FC {
FC::Inclusion(vs)
}
#[allow(dead_code)]
pub fn f_andnot(fc: FC) -> FC {
FC::AndNot(Box::new(fc))
}
#[allow(dead_code)]
pub fn f_self<'a>() -> FC<'a> {
FC::SelfUUID
}
#[allow(dead_code)]
pub fn f_id(id: &str) -> FC<'static> {
let uf = Uuid::parse_str(id)
.ok()
.map(|u| FC::Eq("uuid", PartialValue::new_uuid(u)));
let spnf = PartialValue::new_spn_s(id).map(|spn| FC::Eq("spn", spn));
let nf = FC::Eq("name", PartialValue::new_iname(id));
let f: Vec<_> = iter::once(uf)
.chain(iter::once(spnf))
.filter_map(|v| v)
.chain(iter::once(nf))
.collect();
FC::Or(f)
}
#[allow(dead_code)]
pub fn f_spn_name(id: &str) -> FC<'static> {
let spnf = PartialValue::new_spn_s(id).map(|spn| FC::Eq("spn", spn));
let nf = FC::Eq("name", PartialValue::new_iname(id));
let f: Vec<_> = iter::once(spnf)
.filter_map(|v| v)
.chain(iter::once(nf))
.collect();
FC::Or(f)
}
#[derive(Debug, Deserialize)]
pub enum FC<'a> {
Eq(&'a str, PartialValue),
Sub(&'a str, PartialValue),
Pres(&'a str),
LessThan(&'a str, PartialValue),
Or(Vec<FC<'a>>),
And(Vec<FC<'a>>),
Inclusion(Vec<FC<'a>>),
AndNot(Box<FC<'a>>),
SelfUUID,
}
#[derive(Debug, Clone, PartialEq)]
enum FilterComp {
Eq(String, PartialValue),
Sub(String, PartialValue),
Pres(String),
LessThan(String, PartialValue),
Or(Vec<FilterComp>),
And(Vec<FilterComp>),
Inclusion(Vec<FilterComp>),
AndNot(Box<FilterComp>),
SelfUUID,
}
#[derive(Debug, Clone)]
pub enum FilterResolved {
Eq(String, PartialValue, bool),
Sub(String, PartialValue, bool),
Pres(String, bool),
LessThan(String, PartialValue, bool),
Or(Vec<FilterResolved>),
And(Vec<FilterResolved>),
Inclusion(Vec<FilterResolved>),
AndNot(Box<FilterResolved>),
}
#[derive(Debug, Clone)]
pub struct FilterInvalid {
inner: FilterComp,
}
#[derive(Debug, Clone)]
pub struct FilterValid {
inner: FilterComp,
}
#[derive(Debug, Clone)]
pub struct FilterValidResolved {
inner: FilterResolved,
}
#[derive(Debug)]
pub enum FilterPlan {
Invalid,
EqIndexed(String, String),
EqUnindexed(String),
EqCorrupt(String),
SubIndexed(String, String),
SubUnindexed(String),
SubCorrupt(String),
PresIndexed(String),
PresUnindexed(String),
PresCorrupt(String),
LessThanUnindexed(String),
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(Debug, Clone)]
pub struct Filter<STATE> {
state: STATE,
}
impl Filter<FilterValidResolved> {
pub 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: &Event,
idxmeta: Option<&HashSet<IdxKey>>,
) -> Result<Filter<FilterValidResolved>, OperationError> {
Ok(Filter {
state: FilterValidResolved {
inner: match idxmeta {
Some(idx) => {
let idx_ref: HashSet<(&String, &IndexType)> =
idx.iter().map(|ikey| (&ikey.attr, &ikey.itype)).collect();
FilterResolved::resolve_idx(self.state.inner.clone(), ev, &idx_ref)
}
None => FilterResolved::resolve_no_idx(self.state.inner.clone(), ev),
}
.ok_or(OperationError::FilterUUIDResolution)?,
},
})
}
pub fn get_attr_set(&self) -> BTreeSet<&str> {
let mut r_set = BTreeSet::new();
self.state.inner.get_attr_set(&mut r_set);
r_set
}
}
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 into_ignore_hidden(self) -> Self {
Filter {
state: FilterInvalid {
inner: FilterComp::new_ignore_hidden(self.state.inner),
},
}
}
pub fn new_recycled(inner: FC) -> Self {
let fc = FilterComp::new(inner);
Filter {
state: FilterInvalid {
inner: FilterComp::new_recycled(fc),
},
}
}
pub fn into_recycled(self) -> Self {
Filter {
state: FilterInvalid {
inner: FilterComp::new_recycled(self.state.inner),
},
}
}
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 unsafe fn into_valid_resolved(self) -> Filter<FilterValidResolved> {
let idxmeta = vec![
("uuid".to_string(), IndexType::EQUALITY),
("uuid".to_string(), IndexType::PRESENCE),
("name".to_string(), IndexType::EQUALITY),
("name".to_string(), IndexType::SUBSTRING),
("name".to_string(), IndexType::PRESENCE),
("class".to_string(), IndexType::EQUALITY),
("class".to_string(), IndexType::PRESENCE),
("member".to_string(), IndexType::EQUALITY),
("member".to_string(), IndexType::PRESENCE),
("memberof".to_string(), IndexType::EQUALITY),
("memberof".to_string(), IndexType::PRESENCE),
("directmemberof".to_string(), IndexType::EQUALITY),
("directmemberof".to_string(), 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 unsafe fn into_valid(self) -> Filter<FilterValid> {
Filter {
state: FilterValid {
inner: self.state.inner,
},
}
}
#[cfg(test)]
pub unsafe fn from_str(fc: &str) -> Self {
let f: FC = serde_json::from_str(fc).expect("Failure parsing filter!");
Filter {
state: FilterInvalid {
inner: FilterComp::new(f),
},
}
}
pub fn validate(
&self,
schema: &dyn SchemaTransaction,
) -> Result<Filter<FilterValid>, SchemaError> {
Ok(Filter {
state: FilterValid {
inner: self.state.inner.validate(schema)?,
},
})
}
pub fn from_ro(
audit: &mut AuditScope,
f: &ProtoFilter,
qs: &QueryServerReadTransaction,
) -> Result<Self, OperationError> {
lperf_trace_segment!(audit, "filter::from_ro", || {
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_ro(audit, f, qs)?,
},
})
})
}
pub fn from_rw(
audit: &mut AuditScope,
f: &ProtoFilter,
qs: &QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
lperf_trace_segment!(audit, "filter::from_rw", || {
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_rw(audit, f, qs)?,
},
})
})
}
pub fn from_ldap_ro(
audit: &mut AuditScope,
f: &LdapFilter,
qs: &QueryServerReadTransaction,
) -> Result<Self, OperationError> {
lperf_trace_segment!(audit, "filter::from_ldap_ro", || {
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_ldap_ro(audit, f, qs)?,
},
})
})
}
}
impl FilterComp {
fn new(fc: FC) -> Self {
match fc {
FC::Eq(a, v) => FilterComp::Eq(a.to_string(), v),
FC::Sub(a, v) => FilterComp::Sub(a.to_string(), v),
FC::Pres(a) => FilterComp::Pres(a.to_string()),
FC::LessThan(a, v) => FilterComp::LessThan(a.to_string(), 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("class".to_string(), PartialValue::new_iutf8s("tombstone")),
FilterComp::Eq("class".to_string(), PartialValue::new_iutf8s("recycled")),
]))),
fc,
])
}
fn new_recycled(fc: FilterComp) -> Self {
FilterComp::And(vec![
FilterComp::Eq("class".to_string(), PartialValue::new_iutf8s("recycled")),
fc,
])
}
fn get_attr_set<'a>(&'a self, r_set: &mut BTreeSet<&'a str>) {
match self {
FilterComp::Eq(attr, _) => {
r_set.insert(attr.as_str());
}
FilterComp::Sub(attr, _) => {
r_set.insert(attr.as_str());
}
FilterComp::Pres(attr) => {
r_set.insert(attr.as_str());
}
FilterComp::LessThan(attr, _) => {
r_set.insert(attr.as_str());
}
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("uuid");
}
}
}
pub fn validate(&self, schema: &dyn SchemaTransaction) -> Result<FilterComp, SchemaError> {
let schema_attributes = schema.get_attributes();
match self {
FilterComp::Eq(attr, value) => {
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr_norm.as_str(), &value)
.map(|_| FilterComp::Eq(attr_norm, value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr_norm)),
}
}
FilterComp::Sub(attr, value) => {
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr_norm.as_str(), &value)
.map(|_| FilterComp::Sub(attr_norm, value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr_norm)),
}
}
FilterComp::Pres(attr) => {
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(_attr_name) => {
Ok(FilterComp::Pres(attr_norm))
}
None => Err(SchemaError::InvalidAttribute(attr_norm)),
}
}
FilterComp::LessThan(attr, value) => {
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
schema_a
.validate_partialvalue(attr_norm.as_str(), &value)
.map(|_| FilterComp::LessThan(attr_norm, value.clone()))
}
None => Err(SchemaError::InvalidAttribute(attr_norm)),
}
}
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(
audit: &mut AuditScope,
f: &ProtoFilter,
qs: &QueryServerReadTransaction,
) -> Result<Self, OperationError> {
Ok(match f {
ProtoFilter::Eq(a, v) => {
let nk = qs.get_schema().normalise_attr_name(a);
let v = qs.clone_partialvalue(audit, nk.as_str(), v)?;
FilterComp::Eq(nk, v)
}
ProtoFilter::Sub(a, v) => {
let nk = qs.get_schema().normalise_attr_name(a);
let v = qs.clone_partialvalue(audit, nk.as_str(), v)?;
FilterComp::Sub(nk, v)
}
ProtoFilter::Pres(a) => {
let nk = qs.get_schema().normalise_attr_name(a);
FilterComp::Pres(nk)
}
ProtoFilter::Or(l) => FilterComp::Or(
l.iter()
.map(|f| Self::from_ro(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
ProtoFilter::And(l) => FilterComp::And(
l.iter()
.map(|f| Self::from_ro(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
ProtoFilter::AndNot(l) => FilterComp::AndNot(Box::new(Self::from_ro(audit, l, qs)?)),
ProtoFilter::SelfUUID => FilterComp::SelfUUID,
})
}
fn from_rw(
audit: &mut AuditScope,
f: &ProtoFilter,
qs: &QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
Ok(match f {
ProtoFilter::Eq(a, v) => {
let nk = qs.get_schema().normalise_attr_name(a);
let v = qs.clone_partialvalue(audit, nk.as_str(), v)?;
FilterComp::Eq(nk, v)
}
ProtoFilter::Sub(a, v) => {
let nk = qs.get_schema().normalise_attr_name(a);
let v = qs.clone_partialvalue(audit, nk.as_str(), v)?;
FilterComp::Sub(nk, v)
}
ProtoFilter::Pres(a) => {
let nk = qs.get_schema().normalise_attr_name(a);
FilterComp::Pres(nk)
}
ProtoFilter::Or(l) => FilterComp::Or(
l.iter()
.map(|f| Self::from_rw(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
ProtoFilter::And(l) => FilterComp::And(
l.iter()
.map(|f| Self::from_rw(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
ProtoFilter::AndNot(l) => FilterComp::AndNot(Box::new(Self::from_rw(audit, l, qs)?)),
ProtoFilter::SelfUUID => FilterComp::SelfUUID,
})
}
fn from_ldap_ro(
audit: &mut AuditScope,
f: &LdapFilter,
qs: &QueryServerReadTransaction,
) -> Result<Self, OperationError> {
Ok(match f {
LdapFilter::And(l) => FilterComp::And(
l.iter()
.map(|f| Self::from_ldap_ro(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
LdapFilter::Or(l) => FilterComp::Or(
l.iter()
.map(|f| Self::from_ldap_ro(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
LdapFilter::Not(l) => FilterComp::AndNot(Box::new(Self::from_ldap_ro(audit, l, qs)?)),
LdapFilter::Equality(a, v) => {
let a = ldap_attr_filter_map(a);
let v = qs.clone_partialvalue(audit, a.as_str(), v)?;
FilterComp::Eq(a, v)
}
LdapFilter::Present(a) => FilterComp::Pres(ldap_attr_filter_map(a)),
})
}
}
#[cfg(test)]
impl PartialEq for Filter<FilterInvalid> {
fn eq(&self, rhs: &Filter<FilterInvalid>) -> bool {
self.state.inner == rhs.state.inner
}
}
#[cfg(test)]
impl PartialEq for Filter<FilterValid> {
fn eq(&self, rhs: &Filter<FilterValid>) -> bool {
self.state.inner == rhs.state.inner
}
}
impl PartialEq for Filter<FilterValidResolved> {
fn eq(&self, rhs: &Filter<FilterValidResolved>) -> bool {
self.state.inner == rhs.state.inner
}
}
impl PartialEq for FilterResolved {
fn eq(&self, rhs: &FilterResolved) -> bool {
match (self, rhs) {
(FilterResolved::Eq(a1, v1, i1), FilterResolved::Eq(a2, v2, i2)) => {
a1 == a2 && v1 == v2 && i1 == i2
}
(FilterResolved::Sub(a1, v1, i1), FilterResolved::Sub(a2, v2, i2)) => {
a1 == a2 && v1 == v2 && i1 == i2
}
(FilterResolved::Pres(a1, i1), FilterResolved::Pres(a2, i2)) => a1 == a2 && i1 == i2,
(FilterResolved::And(vs1), FilterResolved::And(vs2)) => vs1 == vs2,
(FilterResolved::Or(vs1), FilterResolved::Or(vs2)) => vs1 == vs2,
(FilterResolved::AndNot(f1), FilterResolved::AndNot(f2)) => f1 == f2,
(_, _) => false,
}
}
}
impl Eq for FilterResolved {}
#[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 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 {
match (self, rhs) {
(FilterResolved::Eq(a1, v1, true), FilterResolved::Eq(a2, v2, true)) => {
match a1.cmp(a2) {
Ordering::Equal => v1.cmp(v2),
o => o,
}
}
(FilterResolved::Sub(a1, v1, true), FilterResolved::Sub(a2, v2, true)) => {
match a1.cmp(a2) {
Ordering::Equal => v1.cmp(v2),
o => o,
}
}
(FilterResolved::Pres(a1, true), FilterResolved::Pres(a2, true)) => a1.cmp(a2),
(FilterResolved::Eq(_, _, true), _) => Ordering::Less,
(_, FilterResolved::Eq(_, _, true)) => Ordering::Greater,
(FilterResolved::Pres(_, true), _) => Ordering::Less,
(_, FilterResolved::Pres(_, true)) => Ordering::Greater,
(FilterResolved::Sub(_, _, true), _) => Ordering::Greater,
(_, FilterResolved::Sub(_, _, true)) => Ordering::Less,
(FilterResolved::Pres(_, false), FilterResolved::Pres(_, false)) => Ordering::Equal,
(FilterResolved::Pres(_, false), _) => Ordering::Less,
(_, FilterResolved::Pres(_, false)) => Ordering::Greater,
(FilterResolved::Eq(_, _, false), FilterResolved::Eq(_, _, false)) => Ordering::Equal,
(FilterResolved::Eq(_, _, false), _) => Ordering::Less,
(_, FilterResolved::Eq(_, _, false)) => Ordering::Greater,
(FilterResolved::Sub(_, _, false), FilterResolved::Sub(_, _, false)) => Ordering::Equal,
(FilterResolved::Sub(_, _, false), _) => Ordering::Greater,
(_, FilterResolved::Sub(_, _, false)) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
}
impl FilterResolved {
#[cfg(test)]
unsafe fn from_invalid(fc: FilterComp, idxmeta: &HashSet<(&String, &IndexType)>) -> Self {
match fc {
FilterComp::Eq(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::EQUALITY));
FilterResolved::Eq(a, v, idx)
}
FilterComp::Sub(a, v) => {
let idx = false;
FilterResolved::Sub(a, v, idx)
}
FilterComp::Pres(a) => {
let idx = idxmeta.contains(&(&a, &IndexType::PRESENCE));
FilterResolved::Pres(a, idx)
}
FilterComp::LessThan(a, v) => {
let idx = false;
FilterResolved::LessThan(a, v, idx)
}
FilterComp::Or(vs) => FilterResolved::Or(
vs.into_iter()
.map(|v| FilterResolved::from_invalid(v, idxmeta))
.collect(),
),
FilterComp::And(vs) => FilterResolved::And(
vs.into_iter()
.map(|v| FilterResolved::from_invalid(v, idxmeta))
.collect(),
),
FilterComp::Inclusion(vs) => FilterResolved::Inclusion(
vs.into_iter()
.map(|v| FilterResolved::from_invalid(v, idxmeta))
.collect(),
),
FilterComp::AndNot(f) => {
FilterResolved::AndNot(Box::new(FilterResolved::from_invalid(
(*f).clone(),
idxmeta,
)))
}
FilterComp::SelfUUID => panic!("Not possible to resolve SelfUUID in from_invalid!"),
}
}
fn resolve_idx(
fc: FilterComp,
ev: &Event,
idxmeta: &HashSet<(&String, &IndexType)>,
) -> Option<Self> {
match fc {
FilterComp::Eq(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::EQUALITY));
Some(FilterResolved::Eq(a, v, idx))
}
FilterComp::Sub(a, v) => {
let idx = idxmeta.contains(&(&a, &IndexType::SUBSTRING));
Some(FilterResolved::Sub(a, v, idx))
}
FilterComp::Pres(a) => {
let idx = idxmeta.contains(&(&a, &IndexType::PRESENCE));
Some(FilterResolved::Pres(a, idx))
}
FilterComp::LessThan(a, v) => {
let idx = false;
Some(FilterResolved::LessThan(a, v, idx))
}
FilterComp::Or(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_idx(f, ev, idxmeta))
.collect();
fi.map(FilterResolved::Or)
}
FilterComp::And(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_idx(f, ev, idxmeta))
.collect();
fi.map(FilterResolved::And)
}
FilterComp::Inclusion(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_idx(f, ev, idxmeta))
.collect();
fi.map(FilterResolved::Inclusion)
}
FilterComp::AndNot(f) => {
FilterResolved::resolve_idx((*f).clone(), ev, idxmeta)
.map(|fi| FilterResolved::AndNot(Box::new(fi)))
}
FilterComp::SelfUUID => match &ev.origin {
EventOrigin::User(e) => {
let uuid_s = "uuid".to_string();
let idx = idxmeta.contains(&(&uuid_s, &IndexType::EQUALITY));
Some(FilterResolved::Eq(
uuid_s,
PartialValue::new_uuid(*e.get_uuid()),
idx,
))
}
_ => None,
},
}
}
fn resolve_no_idx(fc: FilterComp, ev: &Event) -> Option<Self> {
match fc {
FilterComp::Eq(a, v) => Some(FilterResolved::Eq(a, v, false)),
FilterComp::Sub(a, v) => Some(FilterResolved::Sub(a, v, false)),
FilterComp::Pres(a) => Some(FilterResolved::Pres(a, false)),
FilterComp::LessThan(a, v) => Some(FilterResolved::LessThan(a, v, false)),
FilterComp::Or(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_no_idx(f, ev))
.collect();
fi.map(FilterResolved::Or)
}
FilterComp::And(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_no_idx(f, ev))
.collect();
fi.map(FilterResolved::And)
}
FilterComp::Inclusion(vs) => {
let fi: Option<Vec<_>> = vs
.into_iter()
.map(|f| FilterResolved::resolve_no_idx(f, ev))
.collect();
fi.map(FilterResolved::Inclusion)
}
FilterComp::AndNot(f) => {
FilterResolved::resolve_no_idx((*f).clone(), ev)
.map(|fi| FilterResolved::AndNot(Box::new(fi)))
}
FilterComp::SelfUUID => match &ev.origin {
EventOrigin::User(e) => Some(FilterResolved::Eq(
"uuid".to_string(),
PartialValue::new_uuid(*e.get_uuid()),
false,
)),
_ => None,
},
}
}
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| match f {
FilterResolved::Inclusion(_) => true,
_ => false,
});
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();
FilterResolved::Inclusion(f_list_new)
}
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| match f {
FilterResolved::And(_) => true,
_ => false,
});
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.pop().expect("corrupt?")
} else {
f_list_new.sort_unstable();
f_list_new.dedup();
FilterResolved::And(f_list_new)
}
}
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| match f {
FilterResolved::Or(_) => true,
_ => false,
});
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.pop().expect("corrupt?")
} else {
f_list_new.sort_unstable_by(|a, b| b.cmp(a));
f_list_new.dedup();
FilterResolved::Or(f_list_new)
}
}
f => f.clone(),
}
}
pub fn is_andnot(&self) -> bool {
match self {
FilterResolved::AndNot(_) => true,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use crate::entry::{Entry, EntryNew, EntrySealed};
use crate::filter::{Filter, FilterInvalid};
use crate::value::PartialValue;
use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
#[test]
fn test_filter_simple() {
let _filt: Filter<FilterInvalid> = filter!(f_eq("class", PartialValue::new_class("user")));
let _complex_filt: Filter<FilterInvalid> = filter!(f_and!([
f_or!([
f_eq("userid", PartialValue::new_iutf8s("test_a")),
f_eq("userid", PartialValue::new_iutf8s("test_b")),
]),
f_sub("class", PartialValue::new_class("user")),
]));
}
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 = unsafe { f_init.into_valid_resolved() };
let f_init_o = f_init_r.optimise();
let f_init_e = unsafe { f_expect.into_valid_resolved() };
debug!("--");
debug!("init --> {:?}", f_init_r);
debug!("opt --> {:?}", f_init_o);
debug!("expect --> {:?}", f_init_e);
assert!(f_init_o == f_init_e);
}};
}
#[test]
fn test_filter_optimise() {
use env_logger;
::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug");
let _ = env_logger::builder()
.format_timestamp(None)
.format_level(false)
.is_test(true)
.try_init();
filter_optimise_assert!(
f_and(vec![f_and(vec![f_eq(
"class",
PartialValue::new_class("test")
)])]),
f_eq("class", PartialValue::new_class("test"))
);
filter_optimise_assert!(
f_or(vec![f_or(vec![f_eq(
"class",
PartialValue::new_class("test")
)])]),
f_eq("class", PartialValue::new_class("test"))
);
filter_optimise_assert!(
f_and(vec![f_or(vec![f_and(vec![f_eq(
"class",
PartialValue::new_class("test")
)])])]),
f_eq("class", PartialValue::new_class("test"))
);
filter_optimise_assert!(
f_and(vec![
f_and(vec![f_eq("class", PartialValue::new_class("test"))]),
f_sub("class", PartialValue::new_class("te")),
f_pres("class"),
f_eq("class", PartialValue::new_class("test"))
]),
f_and(vec![
f_eq("class", PartialValue::new_class("test")),
f_pres("class"),
f_sub("class", PartialValue::new_class("te")),
])
);
filter_optimise_assert!(
f_and(vec![
f_and(vec![
f_eq("class", PartialValue::new_class("foo")),
f_eq("class", PartialValue::new_class("test")),
f_eq("uid", PartialValue::new_class("bar")),
]),
f_sub("class", PartialValue::new_class("te")),
f_pres("class"),
f_eq("class", PartialValue::new_class("test"))
]),
f_and(vec![
f_eq("class", PartialValue::new_class("foo")),
f_eq("class", PartialValue::new_class("test")),
f_pres("class"),
f_eq("uid", PartialValue::new_class("bar")),
f_sub("class", PartialValue::new_class("te")),
])
);
filter_optimise_assert!(
f_or(vec![
f_eq("class", PartialValue::new_class("test")),
f_pres("class"),
f_sub("class", PartialValue::new_class("te")),
f_or(vec![f_eq("class", PartialValue::new_class("test"))]),
]),
f_or(vec![
f_sub("class", PartialValue::new_class("te")),
f_pres("class"),
f_eq("class", PartialValue::new_class("test"))
])
);
filter_optimise_assert!(
f_or(vec![
f_eq("class", PartialValue::new_class("test")),
f_and(vec![
f_eq("class", PartialValue::new_class("test")),
f_eq("term", PartialValue::new_class("test")),
f_or(vec![f_eq("class", PartialValue::new_class("test"))])
]),
]),
f_or(vec![
f_and(vec![
f_eq("class", PartialValue::new_class("test")),
f_eq("term", PartialValue::new_class("test"))
]),
f_eq("class", PartialValue::new_class("test")),
])
);
}
#[test]
fn test_filter_eq() {
let f_t1a = filter!(f_pres("userid"));
let f_t1b = filter!(f_pres("userid"));
let f_t1c = filter!(f_pres("zzzz"));
assert_eq!(f_t1a == f_t1b, true);
assert_eq!(f_t1a == f_t1c, false);
assert_eq!(f_t1b == f_t1c, false);
let f_t2a = filter!(f_and!([f_pres("userid")]));
let f_t2b = filter!(f_and!([f_pres("userid")]));
let f_t2c = filter!(f_and!([f_pres("zzzz")]));
assert_eq!(f_t2a == f_t2b, true);
assert_eq!(f_t2a == f_t2c, false);
assert_eq!(f_t2b == f_t2c, false);
assert_eq!(f_t2c == f_t1a, false);
assert_eq!(f_t2c == f_t1c, false);
}
#[test]
fn test_filter_ord() {
let f_t1a = unsafe { filter_resolved!(f_pres("userid")) };
let f_t1b = unsafe { filter_resolved!(f_pres("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 = unsafe { filter_resolved!(f_and!([])) };
let f_t2b = unsafe { 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 = unsafe { filter_resolved!(f_eq("userid", PartialValue::new_iutf8s(""))) };
assert_eq!(f_t1a.partial_cmp(&f_t3b), Some(Ordering::Less));
assert_eq!(f_t3b.partial_cmp(&f_t1a), Some(Ordering::Greater));
let f_t4b = unsafe { filter_resolved!(f_sub("userid", PartialValue::new_iutf8s(""))) };
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 = unsafe { filter_resolved!(f_pres("userid")) };
let f_t1b = f_t1a.clone();
let f_t1c = unsafe { filter_resolved!(f_pres("zzzz")) };
assert_eq!(f_t1a == f_t1b, true);
assert_eq!(f_t1a == f_t1c, false);
let f_t2a = unsafe { filter_resolved!(f_and!([f_pres("userid")])) };
let f_t2b = f_t2a.clone();
let f_t2c = unsafe { filter_resolved!(f_and!([f_pres("zzzz")])) };
assert_eq!(f_t2a == f_t2b, true);
assert_eq!(f_t2a == f_t2c, false);
}
#[test]
fn test_lessthan_entry_filter() {
let e: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"userid": ["william"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"gidnumber": ["1000"]
}
}"#,
)
.into_sealed_new()
};
let f_t1a = unsafe { filter_resolved!(f_lt("gidnumber", PartialValue::new_uint32(500))) };
assert!(e.entry_match_no_index(&f_t1a) == false);
let f_t1b = unsafe { filter_resolved!(f_lt("gidnumber", PartialValue::new_uint32(1000))) };
assert!(e.entry_match_no_index(&f_t1b) == false);
let f_t1c = unsafe { filter_resolved!(f_lt("gidnumber", PartialValue::new_uint32(1001))) };
assert!(e.entry_match_no_index(&f_t1c) == true);
}
#[test]
fn test_or_entry_filter() {
let e: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"userid": ["william"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"uidnumber": ["1000"]
}
}"#,
)
.into_sealed_new()
};
let f_t1a = unsafe {
filter_resolved!(f_or!([
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(e.entry_match_no_index(&f_t1a));
let f_t2a = unsafe {
filter_resolved!(f_or!([
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(e.entry_match_no_index(&f_t2a));
let f_t3a = unsafe {
filter_resolved!(f_or!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(e.entry_match_no_index(&f_t3a));
let f_t4a = unsafe {
filter_resolved!(f_or!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(!e.entry_match_no_index(&f_t4a));
}
#[test]
fn test_and_entry_filter() {
let e: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"userid": ["william"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"uidnumber": ["1000"]
}
}"#,
)
.into_sealed_new()
};
let f_t1a = unsafe {
filter_resolved!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(e.entry_match_no_index(&f_t1a));
let f_t2a = unsafe {
filter_resolved!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(!e.entry_match_no_index(&f_t2a));
let f_t3a = unsafe {
filter_resolved!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(!e.entry_match_no_index(&f_t3a));
let f_t4a = unsafe {
filter_resolved!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(!e.entry_match_no_index(&f_t4a));
}
#[test]
fn test_not_entry_filter() {
let e1: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"userid": ["william"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"uidnumber": ["1000"]
}
}"#,
)
.into_sealed_new()
};
let f_t1a = unsafe {
filter_resolved!(f_andnot(f_eq("userid", PartialValue::new_iutf8s("alice"))))
};
assert!(e1.entry_match_no_index(&f_t1a));
let f_t2a = unsafe {
filter_resolved!(f_andnot(f_eq(
"userid",
PartialValue::new_iutf8s("william")
)))
};
assert!(!e1.entry_match_no_index(&f_t2a));
}
#[test]
fn test_nested_entry_filter() {
let e1: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"uidnumber": ["1000"]
}
}"#,
)
.into_sealed_new()
};
let e2: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"uuid": ["4b6228ab-1dbe-42a4-a9f5-f6368222438e"],
"uidnumber": ["1001"]
}
}"#,
)
.into_sealed_new()
};
let e3: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"uuid": ["7b23c99d-c06b-4a9a-a958-3afa56383e1d"],
"uidnumber": ["1002"]
}
}"#,
)
.into_sealed_new()
};
let e4: Entry<EntrySealed, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["group"],
"uuid": ["21d816b5-1f6a-4696-b7c1-6ed06d22ed81"],
"uidnumber": ["1000"]
}
}"#,
)
.into_sealed_new()
};
let f_t1a = unsafe {
filter_resolved!(f_and!([
f_eq("class", PartialValue::new_class("person")),
f_or!([
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
f_eq("uidnumber", PartialValue::new_iutf8s("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("userid");
f_expect.insert("class");
let f_t1a = unsafe {
filter_valid!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("class", PartialValue::new_iutf8s("1001")),
]))
};
assert!(f_t1a.get_attr_set() == f_expect);
let f_t2a = unsafe {
filter_valid!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("class", PartialValue::new_iutf8s("1001")),
f_eq("userid", PartialValue::new_iutf8s("claire")),
]))
};
assert!(f_t2a.get_attr_set() == f_expect);
}
}