use crate::audit::AuditScope;
use crate::credential::Credential;
use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved};
use crate::ldap::ldap_attr_entry_map;
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
use crate::repl::cid::Cid;
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction};
use crate::server::{
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
};
use crate::value::{IndexType, SyntaxType};
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::Entry as ProtoEntry;
use kanidm_proto::v1::Filter as ProtoFilter;
use kanidm_proto::v1::{OperationError, SchemaError};
use crate::be::dbentry::{DbEntry, DbEntryV1, DbEntryVers};
use crate::be::IdxKey;
use ldap3_server::simple::{LdapPartialAttribute, LdapSearchResultEntry};
use std::collections::BTreeSet as Set;
use std::collections::BTreeSet;
use hashbrown::HashMap as Map;
use hashbrown::HashSet;
use uuid::Uuid;
lazy_static! {
static ref CLASS_EXTENSIBLE: PartialValue = PartialValue::new_class("extensibleobject");
static ref PVCLASS_TOMBSTONE: PartialValue = PartialValue::new_class("tombstone");
static ref PVCLASS_RECYCLED: PartialValue = PartialValue::new_class("recycled");
}
#[derive(Clone, Debug)]
pub struct EntryNew; #[derive(Clone, Debug)]
pub struct EntryCommitted {
id: u64,
}
#[derive(Clone, Debug)]
pub struct EntryInit;
#[derive(Clone, Debug)]
pub struct EntryInvalid {
cid: Cid,
}
#[derive(Clone, Debug)]
pub struct EntryValid {
uuid: Uuid,
cid: Cid,
}
#[derive(Clone, Debug)]
pub struct EntrySealed {
uuid: Uuid,
}
#[derive(Clone, Debug)]
pub struct EntryReduced {
uuid: Uuid,
}
fn compare_attrs(left: &Map<String, Set<Value>>, right: &Map<String, Set<Value>>) -> bool {
let allkeys: Set<&str> = left
.keys()
.filter(|k| k != &"last_modified_cid")
.chain(right.keys().filter(|k| k != &"last_modified_cid"))
.map(|s| s.as_str())
.collect();
allkeys.into_iter().all(|k| {
left.get(k) == right.get(k)
})
}
pub struct Entry<VALID, STATE> {
valid: VALID,
state: STATE,
attrs: Map<String, Set<Value>>,
}
impl<VALID, STATE> std::fmt::Debug for Entry<VALID, STATE>
where
STATE: std::fmt::Debug,
VALID: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Entry<EntrySealed, _>")
.field("state", &self.state)
.field("valid", &self.valid)
.finish()
}
}
impl<STATE> std::fmt::Display for Entry<EntrySealed, STATE> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.get_uuid())
}
}
impl<STATE> std::fmt::Display for Entry<EntryInit, STATE> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Entry in initial state")
}
}
impl<STATE> Entry<EntryInit, STATE> {
pub(crate) fn get_uuid(&self) -> Option<&Uuid> {
match self.attrs.get("uuid") {
Some(vs) => match vs.iter().take(1).next() {
Some(uv) => uv.to_uuid(),
_ => None,
},
None => None,
}
}
}
impl Entry<EntryInit, EntryNew> {
#[cfg(test)]
pub fn new() -> Self {
Entry {
valid: EntryInit,
state: EntryNew,
attrs: Map::with_capacity(32),
}
}
pub fn from_proto_entry(
audit: &mut AuditScope,
e: &ProtoEntry,
qs: &QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
let map2: Result<Map<String, Set<Value>>, OperationError> = e
.attrs
.iter()
.map(|(k, v)| {
let nk = qs.get_schema().normalise_attr_name(k);
let nv: Result<Set<Value>, _> =
v.iter().map(|vr| qs.clone_value(audit, &nk, vr)).collect();
match nv {
Ok(nvi) => Ok((nk, nvi)),
Err(e) => Err(e),
}
})
.collect();
let x = map2?;
Ok(Entry {
state: EntryNew,
valid: EntryInit,
attrs: x,
})
}
pub fn from_proto_entry_str(
audit: &mut AuditScope,
es: &str,
qs: &QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
if cfg!(test) {
if es.len() > 256 {
let (dsp_es, _) = es.split_at(255);
ltrace!(audit, "Parsing -> {}...", dsp_es);
} else {
ltrace!(audit, "Parsing -> {}", es);
}
}
let pe: ProtoEntry = serde_json::from_str(es).map_err(|e| {
ladmin_error!(audit, "SerdeJson Failure -> {:?}", e);
OperationError::SerdeJsonError
})?;
Self::from_proto_entry(audit, &pe, qs)
}
#[cfg(test)]
pub(crate) fn unsafe_from_entry_str(es: &str) -> Self {
let pe: ProtoEntry = serde_json::from_str(es).expect("Invalid Proto Entry");
let x: Map<String, Set<Value>> = pe.attrs.into_iter()
.map(|(k, vs)| {
let attr = k.to_lowercase();
let vv: Set<Value> = match attr.as_str() {
"attributename" | "classname" | "domain" => {
vs.into_iter().map(|v| Value::new_iutf8(v)).collect()
}
"name" | "domain_name" => {
vs.into_iter().map(|v| Value::new_iname(v)).collect()
}
"userid" | "uidnumber" => {
warn!("WARNING: Use of unstabilised attributes userid/uidnumber");
vs.into_iter().map(|v| Value::new_iutf8(v)).collect()
}
"class" | "acp_create_class" | "acp_modify_class" => {
vs.into_iter().map(|v| Value::new_class(v.as_str())).collect()
}
"acp_create_attr" | "acp_search_attr" | "acp_modify_removedattr" | "acp_modify_presentattr" |
"systemmay" | "may" | "systemmust" | "must"
=> {
vs.into_iter().map(|v| Value::new_attr(v.as_str())).collect()
}
"uuid" | "domain_uuid" => {
vs.into_iter().map(|v| Value::new_uuids(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect attribute to be presented UTF8 string");
Value::new_utf8(v)
})
).collect()
}
"member" | "memberof" | "directmemberof" => {
vs.into_iter().map(|v| Value::new_refer_s(v.as_str()).unwrap() ).collect()
}
"acp_enable" | "multivalue" | "unique" => {
vs.into_iter().map(|v| Value::new_bools(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect attribute to be presented UTF8 string");
Value::new_utf8(v)
})
).collect()
}
"syntax" => {
vs.into_iter().map(|v| Value::new_syntaxs(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect attribute to be presented UTF8 string");
Value::new_utf8(v)
})
).collect()
}
"index" => {
vs.into_iter().map(|v| Value::new_indexs(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect attribute to be presented UTF8 string");
Value::new_utf8(v)
})
).collect()
}
"acp_targetscope" | "acp_receiver" => {
vs.into_iter().map(|v| Value::new_json_filter(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect attribute to be presented UTF8 string");
Value::new_utf8(v)
})
).collect()
}
"displayname" | "description" => {
vs.into_iter().map(|v| Value::new_utf8(v)).collect()
}
"spn" => {
vs.into_iter().map(|v| {
Value::new_spn_parse(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect SPN attribute to be presented UTF8 string");
Value::new_utf8(v)
})
}).collect()
}
"gidnumber" | "version" => {
vs.into_iter().map(|v| {
Value::new_uint32_str(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect UINT32 attribute to be presented UTF8 string");
Value::new_utf8(v)
})
}).collect()
}
ia => {
warn!("WARNING: Allowing invalid attribute {} to be interpretted as UTF8 string. YOU MAY ENCOUNTER ODD BEHAVIOUR!!!", ia);
vs.into_iter().map(|v| Value::new_utf8(v)).collect()
}
};
(attr, vv)
})
.collect();
Entry {
valid: EntryInit,
state: EntryNew,
attrs: x,
}
}
pub fn assign_cid(mut self, cid: Cid) -> Entry<EntryInvalid, EntryNew> {
self.set_last_changed(cid.clone());
Entry {
valid: EntryInvalid { cid },
state: EntryNew,
attrs: self.attrs,
}
}
pub fn compare(&self, rhs: &Entry<EntrySealed, EntryCommitted>) -> bool {
compare_attrs(&self.attrs, &rhs.attrs)
}
#[cfg(test)]
pub unsafe fn into_invalid_new(mut self) -> Entry<EntryInvalid, EntryNew> {
self.set_last_changed(Cid::new_zero());
Entry {
valid: EntryInvalid {
cid: Cid::new_zero(),
},
state: EntryNew,
attrs: self.attrs,
}
}
#[cfg(test)]
pub unsafe fn into_valid_new(self) -> Entry<EntryValid, EntryNew> {
Entry {
valid: EntryValid {
cid: Cid::new_zero(),
uuid: self.get_uuid().expect("Invalid uuid").clone(),
},
state: EntryNew,
attrs: self.attrs,
}
}
#[cfg(test)]
pub unsafe fn into_sealed_committed(self) -> Entry<EntrySealed, EntryCommitted> {
let uuid = self
.get_uuid()
.and_then(|u| Some(u.clone()))
.unwrap_or_else(|| Uuid::new_v4());
Entry {
valid: EntrySealed { uuid },
state: EntryCommitted { id: 0 },
attrs: self.attrs,
}
}
#[cfg(test)]
pub unsafe fn into_sealed_new(self) -> Entry<EntrySealed, EntryNew> {
Entry {
valid: EntrySealed {
uuid: self.get_uuid().expect("Invalid uuid").clone(),
},
state: EntryNew,
attrs: self.attrs,
}
}
#[cfg(test)]
pub fn add_ava(&mut self, attr: &str, value: Value) {
self.add_ava_int(attr, value)
}
}
impl<STATE> Entry<EntryInvalid, STATE> {
pub(crate) fn get_uuid(&self) -> Option<&Uuid> {
match self.attrs.get("uuid") {
Some(vs) => match vs.iter().take(1).next() {
Some(uv) => uv.to_uuid(),
_ => None,
},
None => None,
}
}
pub fn validate(
self,
schema: &dyn SchemaTransaction,
) -> Result<Entry<EntryValid, STATE>, SchemaError> {
let schema_classes = schema.get_classes();
let schema_attributes = schema.get_attributes();
let uuid: Uuid = match &self.attrs.get("uuid") {
Some(vs) => match vs.iter().take(1).next() {
Some(uuid_v) => match uuid_v.to_uuid() {
Some(uuid) => *uuid,
None => return Err(SchemaError::InvalidAttribute("uuid".to_string())),
},
None => return Err(SchemaError::MissingMustAttribute(vec!["uuid".to_string()])),
},
None => return Err(SchemaError::MissingMustAttribute(vec!["uuid".to_string()])),
};
let ne = Entry {
valid: EntryValid {
uuid,
cid: self.valid.cid,
},
state: self.state,
attrs: self.attrs,
};
{
if !ne.attribute_pres("class") {
return Err(SchemaError::NoClassFound);
}
let extensible = ne.attribute_value_pres("class", &CLASS_EXTENSIBLE);
let entry_classes = ne.get_ava_set("class").ok_or(SchemaError::NoClassFound)?;
let mut invalid_classes = Vec::with_capacity(0);
let mut classes: Vec<&SchemaClass> = Vec::with_capacity(entry_classes.len());
entry_classes.iter().for_each(|c: &Value| {
match c.to_str() {
Some(s) => match schema_classes.get(s) {
Some(x) => classes.push(x),
None => invalid_classes.push(s.to_string()),
},
None => invalid_classes.push("corrupt classname".to_string()),
}
});
if !invalid_classes.is_empty() {
return Err(SchemaError::InvalidClass(invalid_classes));
};
let must: Result<Vec<&SchemaAttribute>, _> = classes
.iter()
.flat_map(|cls| cls.systemmust.iter().chain(cls.must.iter()))
.map(|s| {
Ok(schema_attributes.get(s).ok_or(SchemaError::Corrupted)?)
})
.collect();
let must = must?;
let mut missing_must = Vec::with_capacity(0);
must.iter().for_each(|attr| {
let avas = ne.get_ava(&attr.name);
if avas.is_none() {
missing_must.push(attr.name.clone());
}
});
if !missing_must.is_empty() {
return Err(SchemaError::MissingMustAttribute(missing_must));
}
if extensible {
ne.attrs.iter().try_for_each(|(attr_name, avas)| {
match schema_attributes.get(attr_name) {
Some(a_schema) => {
if a_schema.phantom {
Err(SchemaError::PhantomAttribute(attr_name.clone()))
} else {
a_schema.validate_ava(attr_name.as_str(), avas)
}
}
None => {
Err(SchemaError::InvalidAttribute(attr_name.clone()))
}
}
})?;
} else {
let may: Result<Map<&String, &SchemaAttribute>, _> = classes
.iter()
.flat_map(|cls| {
cls.systemmust
.iter()
.chain(cls.must.iter())
.chain(cls.systemmay.iter())
.chain(cls.may.iter())
})
.map(|s| {
Ok((s, schema_attributes.get(s).ok_or(SchemaError::Corrupted)?))
})
.collect();
let may = may?;
ne.attrs.iter().try_for_each(|(attr_name, avas)| {
match may.get(attr_name) {
Some(a_schema) => {
a_schema.validate_ava(attr_name.as_str(), avas)
}
None => {
Err(SchemaError::InvalidAttribute(attr_name.clone()))
}
}
})?;
}
}
Ok(ne)
}
}
impl<VALID, STATE> Clone for Entry<VALID, STATE>
where
VALID: Clone,
STATE: Clone,
{
fn clone(&self) -> Entry<VALID, STATE> {
Entry {
valid: self.valid.clone(),
state: self.state.clone(),
attrs: self.attrs.clone(),
}
}
}
impl Entry<EntryInvalid, EntryCommitted> {
#[cfg(test)]
pub unsafe fn into_valid_new(self) -> Entry<EntryValid, EntryNew> {
let uuid = self.get_uuid().expect("Invalid uuid").clone();
Entry {
valid: EntryValid {
cid: self.valid.cid,
uuid,
},
state: EntryNew,
attrs: self.attrs,
}
}
pub fn into_recycled(mut self) -> Self {
self.add_ava("class", Value::new_class("recycled"));
Entry {
valid: self.valid.clone(),
state: self.state,
attrs: self.attrs,
}
}
}
impl Entry<EntryInvalid, EntryNew> {
#[cfg(test)]
pub unsafe fn into_valid_new(self) -> Entry<EntryValid, EntryNew> {
let uuid = self.get_uuid().expect("Invalid uuid").clone();
Entry {
valid: EntryValid {
cid: self.valid.cid,
uuid,
},
state: EntryNew,
attrs: self.attrs,
}
}
#[cfg(test)]
pub unsafe fn into_sealed_committed(self) -> Entry<EntrySealed, EntryCommitted> {
let uuid = self
.get_uuid()
.and_then(|u| Some(u.clone()))
.unwrap_or_else(|| Uuid::new_v4());
Entry {
valid: EntrySealed { uuid },
state: EntryCommitted { id: 0 },
attrs: self.attrs,
}
}
#[cfg(test)]
pub unsafe fn into_valid_committed(self) -> Entry<EntryValid, EntryCommitted> {
let uuid = self
.get_uuid()
.and_then(|u| Some(u.clone()))
.unwrap_or_else(|| Uuid::new_v4());
Entry {
valid: EntryValid {
cid: self.valid.cid,
uuid,
},
state: EntryCommitted { id: 0 },
attrs: self.attrs,
}
}
}
impl Entry<EntryInvalid, EntryCommitted> {
#[cfg(test)]
pub unsafe fn into_sealed_committed(self) -> Entry<EntrySealed, EntryCommitted> {
let uuid = self
.get_uuid()
.and_then(|u| Some(u.clone()))
.unwrap_or_else(|| Uuid::new_v4());
Entry {
valid: EntrySealed { uuid },
state: self.state,
attrs: self.attrs,
}
}
}
impl Entry<EntrySealed, EntryNew> {
#[cfg(test)]
pub unsafe fn into_sealed_committed(self) -> Entry<EntrySealed, EntryCommitted> {
Entry {
valid: self.valid,
state: EntryCommitted { id: 0 },
attrs: self.attrs,
}
}
pub fn into_sealed_committed_id(self, id: u64) -> Entry<EntrySealed, EntryCommitted> {
Entry {
valid: self.valid,
state: EntryCommitted { id },
attrs: self.attrs,
}
}
pub fn compare(&self, rhs: &Entry<EntrySealed, EntryNew>) -> bool {
compare_attrs(&self.attrs, &rhs.attrs)
}
}
type IdxDiff<'a> =
Vec<Result<(&'a String, &'a IndexType, String), (&'a String, &'a IndexType, String)>>;
impl<VALID> Entry<VALID, EntryCommitted> {
pub fn get_id(&self) -> u64 {
self.state.id
}
}
impl<STATE> Entry<EntrySealed, STATE> {
pub fn into_init(self) -> Entry<EntryInit, STATE> {
Entry {
valid: EntryInit,
state: self.state,
attrs: self.attrs,
}
}
}
impl Entry<EntrySealed, EntryCommitted> {
#[cfg(test)]
pub unsafe fn into_sealed_committed(self) -> Entry<EntrySealed, EntryCommitted> {
self
}
pub fn compare(&self, rhs: &Entry<EntrySealed, EntryCommitted>) -> bool {
compare_attrs(&self.attrs, &rhs.attrs)
}
pub fn to_dbentry(&self) -> DbEntry {
DbEntry {
ent: DbEntryVers::V1(DbEntryV1 {
attrs: self
.attrs
.iter()
.map(|(k, vs)| {
let dbvs: Vec<_> = vs.iter().map(|v| v.to_db_valuev1()).collect();
(k.clone(), dbvs)
})
.collect(),
}),
}
}
#[inline]
fn get_name2uuid_cands(&self) -> Set<String> {
let cands = ["spn", "name", "gidnumber"];
cands
.iter()
.filter_map(|c| {
self.attrs
.get(*c)
.map(|avs| avs.iter().map(|v| v.to_proto_string_clone()))
})
.flatten()
.collect()
}
#[inline]
pub(crate) fn get_uuid2spn(&self) -> Value {
self.attrs
.get("spn")
.and_then(|vs| vs.iter().take(1).next().cloned())
.or_else(|| {
self.attrs
.get("name")
.and_then(|vs| vs.iter().take(1).next().cloned())
})
.unwrap_or_else(|| Value::new_uuidr(self.get_uuid()))
}
#[inline]
fn get_uuid2rdn(&self) -> String {
self.attrs
.get("spn")
.and_then(|vs| {
vs.iter()
.take(1)
.next()
.map(|v| format!("spn={}", v.to_proto_string_clone()))
})
.or_else(|| {
self.attrs.get("name").and_then(|vs| {
vs.iter()
.take(1)
.next()
.map(|v| format!("name={}", v.to_proto_string_clone()))
})
})
.unwrap_or_else(|| format!("uuid={}", self.get_uuid().to_hyphenated_ref()))
}
#[inline]
pub(crate) fn mask_recycled_ts(&self) -> Option<&Self> {
match self.attrs.get("class") {
Some(cls) => {
if cls.contains(&PVCLASS_TOMBSTONE as &PartialValue)
|| cls.contains(&PVCLASS_RECYCLED as &PartialValue)
{
None
} else {
Some(self)
}
}
None => Some(self),
}
}
pub(crate) fn idx_name2uuid_diff(
pre: Option<&Self>,
post: Option<&Self>,
) -> (
// Add
Option<Set<String>>,
// Remove
Option<Set<String>>,
) {
match (pre, post) {
(None, None) => {
(None, None)
}
(None, Some(b)) => {
(Some(b.get_name2uuid_cands()), None)
}
(Some(a), None) => {
(None, Some(a.get_name2uuid_cands()))
}
(Some(a), Some(b)) => {
let pre_set = a.get_name2uuid_cands();
let post_set = b.get_name2uuid_cands();
let add_set: Set<_> = post_set.difference(&pre_set).cloned().collect();
let rem_set: Set<_> = pre_set.difference(&post_set).cloned().collect();
(Some(add_set), Some(rem_set))
}
}
}
pub(crate) fn idx_uuid2spn_diff(
pre: Option<&Self>,
post: Option<&Self>,
) -> Option<Result<Value, ()>> {
match (pre, post) {
(None, None) => {
None
}
(None, Some(b)) => {
Some(Ok(b.get_uuid2spn()))
}
(Some(_a), None) => {
Some(Err(()))
}
(Some(a), Some(b)) => {
let ia = a.get_uuid2spn();
let ib = b.get_uuid2spn();
if ia != ib {
Some(Ok(ib))
} else {
None
}
}
}
}
pub(crate) fn idx_uuid2rdn_diff(
pre: Option<&Self>,
post: Option<&Self>,
) -> Option<Result<String, ()>> {
match (pre, post) {
(None, None) => {
None
}
(None, Some(b)) => {
Some(Ok(b.get_uuid2rdn()))
}
(Some(_a), None) => {
Some(Err(()))
}
(Some(a), Some(b)) => {
let ia = a.get_uuid2rdn();
let ib = b.get_uuid2rdn();
if ia != ib {
Some(Ok(ib))
} else {
None
}
}
}
}
pub(crate) fn idx_diff<'a>(
idxmeta: &'a HashSet<IdxKey>,
pre: Option<&Self>,
post: Option<&Self>,
) -> IdxDiff<'a> {
match (pre, post) {
(None, None) => {
Vec::new()
}
(Some(pre_e), None) => {
idxmeta
.iter()
.flat_map(|ikey| {
match pre_e.get_ava(ikey.attr.as_str()) {
None => Vec::new(),
Some(vs) => {
let changes: Vec<Result<_, _>> = match ikey.itype {
IndexType::EQUALITY => {
vs.flat_map(|v| {
v.generate_idx_eq_keys().into_iter().map(|idx_key| {
Err((&ikey.attr, &ikey.itype, idx_key))
})
})
.collect()
}
IndexType::PRESENCE => {
vec![Err((&ikey.attr, &ikey.itype, "_".to_string()))]
}
IndexType::SUBSTRING => Vec::new(),
};
changes
}
}
})
.collect()
}
(None, Some(post_e)) => {
idxmeta
.iter()
.flat_map(|ikey| {
match post_e.get_ava(ikey.attr.as_str()) {
None => Vec::new(),
Some(vs) => {
let changes: Vec<Result<_, _>> = match ikey.itype {
IndexType::EQUALITY => {
vs.flat_map(|v| {
v.generate_idx_eq_keys().into_iter().map(|idx_key| {
Ok((&ikey.attr, &ikey.itype, idx_key))
})
})
.collect()
}
IndexType::PRESENCE => {
vec![Ok((&ikey.attr, &ikey.itype, "_".to_string()))]
}
IndexType::SUBSTRING => Vec::new(),
};
changes
}
}
})
.collect()
}
(Some(pre_e), Some(post_e)) => {
assert!(pre_e.state.id == post_e.state.id);
idxmeta
.iter()
.flat_map(|ikey| {
match (
pre_e.get_ava_set(ikey.attr.as_str()),
post_e.get_ava_set(ikey.attr.as_str()),
) {
(None, None) => {
Vec::new()
}
(Some(pre_vs), None) => {
let changes: Vec<Result<_, _>> = match ikey.itype {
IndexType::EQUALITY => {
pre_vs
.iter()
.flat_map(|v| {
v.generate_idx_eq_keys().into_iter().map(
|idx_key| {
Err((&ikey.attr, &ikey.itype, idx_key))
},
)
})
.collect()
}
IndexType::PRESENCE => {
vec![Err((&ikey.attr, &ikey.itype, "_".to_string()))]
}
IndexType::SUBSTRING => Vec::new(),
};
changes
}
(None, Some(post_vs)) => {
let changes: Vec<Result<_, _>> = match ikey.itype {
IndexType::EQUALITY => {
post_vs
.iter()
.flat_map(|v| {
v.generate_idx_eq_keys().into_iter().map(
|idx_key| {
Ok((&ikey.attr, &ikey.itype, idx_key))
},
)
})
.collect()
}
IndexType::PRESENCE => {
vec![Ok((&ikey.attr, &ikey.itype, "_".to_string()))]
}
IndexType::SUBSTRING => Vec::new(),
};
changes
}
(Some(pre_vs), Some(post_vs)) => {
pre_vs
.difference(&post_vs)
.map(|pre_v| {
match ikey.itype {
IndexType::EQUALITY => {
pre_v
.generate_idx_eq_keys()
.into_iter()
.map(|idx_key| {
Err((&ikey.attr, &ikey.itype, idx_key))
})
.collect()
}
IndexType::PRESENCE => {
Vec::new()
}
IndexType::SUBSTRING => Vec::new(),
}
})
.chain(post_vs.difference(&pre_vs).map(|post_v| {
match ikey.itype {
IndexType::EQUALITY => {
post_v
.generate_idx_eq_keys()
.into_iter()
.map(|idx_key| {
Ok((&ikey.attr, &ikey.itype, idx_key))
})
.collect()
}
IndexType::PRESENCE => {
Vec::new()
}
IndexType::SUBSTRING => Vec::new(),
}
}))
.flatten() .collect() }
}
})
.collect()
}
}
}
pub fn from_dbentry(au: &mut AuditScope, db_e: DbEntry, id: u64) -> Result<Self, ()> {
let r_attrs: Result<Map<String, Set<Value>>, ()> = match db_e.ent {
DbEntryVers::V1(v1) => v1
.attrs
.into_iter()
.map(|(k, vs)| {
let vv: Result<Set<Value>, ()> =
vs.into_iter().map(Value::from_db_valuev1).collect();
match vv {
Ok(vv) => Ok((k, vv)),
Err(()) => {
ladmin_error!(au, "from_dbentry failed on value {:?}", k);
Err(())
}
}
})
.collect(),
};
let attrs = r_attrs?;
let uuid: Uuid = *match attrs.get("uuid") {
Some(vs) => vs.iter().take(1).next(),
None => None,
}
.ok_or(())?
.to_uuid()
.ok_or(())?;
Ok(Entry {
valid: EntrySealed { uuid },
state: EntryCommitted { id },
attrs,
})
}
pub unsafe fn into_reduced(self) -> Entry<EntryReduced, EntryCommitted> {
Entry {
valid: EntryReduced {
uuid: self.valid.uuid,
},
state: self.state,
attrs: self.attrs,
}
}
pub fn reduce_attributes(
self,
allowed_attrs: BTreeSet<&str>,
) -> Entry<EntryReduced, EntryCommitted> {
let Entry {
valid: s_valid,
state: s_state,
attrs: s_attrs,
} = self;
let f_attrs: Map<_, _> = s_attrs
.into_iter()
.filter_map(|(k, v)| {
if allowed_attrs.contains(k.as_str()) {
Some((k, v))
} else {
None
}
})
.collect();
Entry {
valid: EntryReduced { uuid: s_valid.uuid },
state: s_state,
attrs: f_attrs,
}
}
pub fn to_tombstone(&self, cid: Cid) -> Entry<EntryInvalid, EntryCommitted> {
let class_ava = btreeset![Value::new_class("object"), Value::new_class("tombstone")];
let last_mod_ava = btreeset![Value::new_cid(cid.clone())];
let mut attrs_new: Map<String, Set<Value>> = Map::new();
attrs_new.insert(
"uuid".to_string(),
btreeset![Value::new_uuidr(&self.get_uuid())],
);
attrs_new.insert("class".to_string(), class_ava);
attrs_new.insert("last_modified_cid".to_string(), last_mod_ava);
Entry {
valid: EntryInvalid { cid },
state: self.state.clone(),
attrs: attrs_new,
}
}
pub fn into_valid(self, cid: Cid) -> Entry<EntryValid, EntryCommitted> {
Entry {
valid: EntryValid {
uuid: self.valid.uuid,
cid,
},
state: self.state,
attrs: self.attrs,
}
}
}
impl<STATE> Entry<EntryValid, STATE> {
pub fn invalidate(self) -> Entry<EntryInvalid, STATE> {
Entry {
valid: EntryInvalid {
cid: self.valid.cid,
},
state: self.state,
attrs: self.attrs,
}
}
pub fn seal(self) -> Entry<EntrySealed, STATE> {
Entry {
valid: EntrySealed {
uuid: self.valid.uuid,
},
state: self.state,
attrs: self.attrs,
}
}
pub fn get_uuid(&self) -> &Uuid {
&self.valid.uuid
}
}
impl<STATE> Entry<EntrySealed, STATE> {
pub fn invalidate(mut self, cid: Cid) -> Entry<EntryInvalid, STATE> {
self.set_last_changed(cid.clone());
Entry {
valid: EntryInvalid { cid },
state: self.state,
attrs: self.attrs,
}
}
pub fn get_uuid(&self) -> &Uuid {
&self.valid.uuid
}
#[cfg(test)]
pub unsafe fn into_invalid(mut self) -> Entry<EntryInvalid, STATE> {
self.set_last_changed(Cid::new_zero());
Entry {
valid: EntryInvalid {
cid: Cid::new_zero(),
},
state: self.state,
attrs: self.attrs,
}
}
}
impl Entry<EntryReduced, EntryCommitted> {
pub fn get_uuid(&self) -> &Uuid {
&self.valid.uuid
}
pub fn to_pe(
&self,
audit: &mut AuditScope,
qs: &QueryServerReadTransaction,
) -> Result<ProtoEntry, OperationError> {
let attrs: Result<_, _> = self
.attrs
.iter()
.map(|(k, vs)| {
let pvs: Result<Vec<String>, _> =
vs.iter().map(|v| qs.resolve_value(audit, v)).collect();
let pvs = pvs?;
Ok((k.clone(), pvs))
})
.collect();
Ok(ProtoEntry { attrs: attrs? })
}
pub fn to_ldap(
&self,
audit: &mut AuditScope,
qs: &QueryServerReadTransaction,
basedn: &str,
) -> Result<LdapSearchResultEntry, OperationError> {
let rdn = qs.uuid_to_rdn(audit, self.get_uuid())?;
let dn = format!("{},{}", rdn, basedn);
let attributes: Result<Vec<_>, _> = self
.attrs
.iter()
.map(|(k, vs)| {
let pvs: Result<Vec<String>, _> = vs
.iter()
.map(|v| qs.resolve_value_ldap(audit, v, basedn))
.collect();
let pvs = pvs?;
let ks = ldap_attr_entry_map(k.as_str());
Ok(LdapPartialAttribute {
atype: ks,
vals: pvs,
})
})
.collect();
let attributes = attributes?;
Ok(LdapSearchResultEntry { dn, attributes })
}
}
impl<VALID, STATE> Entry<VALID, STATE> {
fn add_ava_int(&mut self, attr: &str, value: Value) {
let v = self.attrs.entry(attr.to_string()).or_insert_with(Set::new);
v.insert(value);
}
fn set_last_changed(&mut self, cid: Cid) {
let cv = btreeset![Value::new_cid(cid)];
let _ = self.attrs.insert("last_modified_cid".to_string(), cv);
}
#[inline(always)]
pub fn get_ava_names(&self) -> impl Iterator<Item = &str> {
self.attrs.keys().map(|a| a.as_str())
}
#[inline(always)]
pub fn get_ava(&self, attr: &str) -> Option<impl Iterator<Item = &Value>> {
self.attrs.get(attr).map(|vs| vs.iter())
}
#[inline(always)]
pub fn get_ava_set(&self, attr: &str) -> Option<&Set<Value>> {
self.attrs.get(attr)
}
#[inline(always)]
pub fn get_ava_as_str(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
self.get_ava(attr).map(|i| i.filter_map(|s| s.to_str()))
}
#[inline(always)]
pub fn get_ava_as_refuuid(&self, attr: &str) -> Option<impl Iterator<Item = &Uuid>> {
self.get_ava(attr)
.map(|i| i.filter_map(|v| v.to_ref_uuid()))
}
#[inline(always)]
pub fn get_ava_iter_sshpubkeys(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
self.get_ava(attr).map(|i| i.filter_map(|v| v.get_sshkey()))
}
#[inline(always)]
pub(crate) fn get_ava_opt_index(&self, attr: &str) -> Result<Vec<&IndexType>, ()> {
match self.attrs.get(attr) {
Some(av) => {
let r: Result<Vec<_>, _> = av.iter().map(|v| v.to_indextype().ok_or(())).collect();
r
}
None => Ok(Vec::new()),
}
}
#[inline(always)]
pub fn get_ava_single(&self, attr: &str) -> Option<&Value> {
match self.attrs.get(attr) {
Some(vs) => {
if vs.len() != 1 {
None
} else {
vs.iter().take(1).next()
}
}
None => None,
}
}
#[inline(always)]
pub fn get_ava_single_bool(&self, attr: &str) -> Option<bool> {
self.get_ava_single(attr).and_then(|a| a.to_bool())
}
#[inline(always)]
pub fn get_ava_single_uint32(&self, attr: &str) -> Option<u32> {
self.get_ava_single(attr).and_then(|a| a.to_uint32())
}
#[inline(always)]
pub fn get_ava_single_syntax(&self, attr: &str) -> Option<&SyntaxType> {
self.get_ava_single(attr).and_then(|a| a.to_syntaxtype())
}
#[inline(always)]
pub fn get_ava_single_credential(&self, attr: &str) -> Option<&Credential> {
self.get_ava_single(attr).and_then(|a| a.to_credential())
}
#[inline(always)]
pub fn get_ava_single_radiuscred(&self, attr: &str) -> Option<&str> {
self.get_ava_single(attr)
.and_then(|a| a.get_radius_secret())
}
#[inline(always)]
pub fn get_ava_single_str(&self, attr: &str) -> Option<&str> {
self.get_ava_single(attr).and_then(|v| v.to_str())
}
#[inline(always)]
pub fn get_ava_single_protofilter(&self, attr: &str) -> Option<&ProtoFilter> {
self.get_ava_single(attr)
.and_then(|v: &Value| v.as_json_filter())
}
#[inline(always)]
pub(crate) fn generate_spn(&self, domain_name: &str) -> Option<Value> {
self.get_ava_single_str("name")
.map(|name| Value::new_spn_str(name, domain_name))
}
#[inline(always)]
pub fn attribute_pres(&self, attr: &str) -> bool {
self.attrs.contains_key(attr)
}
#[inline(always)]
pub fn attribute_value_pres(&self, attr: &str, value: &PartialValue) -> bool {
self.attribute_equality(attr, value)
}
#[inline(always)]
pub fn attribute_equality(&self, attr: &str, value: &PartialValue) -> bool {
match self.attrs.get(attr) {
Some(v_list) => v_list.contains(value),
None => false,
}
}
#[inline(always)]
pub fn attribute_substring(&self, attr: &str, subvalue: &PartialValue) -> bool {
match self.attrs.get(attr) {
Some(v_list) => v_list
.iter()
.fold(false, |acc, v| if acc { acc } else { v.contains(subvalue) }),
None => false,
}
}
#[inline(always)]
pub fn attribute_lessthan(&self, attr: &str, subvalue: &PartialValue) -> bool {
match self.attrs.get(attr) {
Some(v_list) => v_list
.iter()
.fold(false, |acc, v| if acc { acc } else { v.lessthan(subvalue) }),
None => false,
}
}
#[inline(always)]
pub fn entry_match_no_index(&self, filter: &Filter<FilterValidResolved>) -> bool {
self.entry_match_no_index_inner(filter.to_inner())
}
fn entry_match_no_index_inner(&self, filter: &FilterResolved) -> bool {
match filter {
FilterResolved::Eq(attr, value, _) => self.attribute_equality(attr.as_str(), value),
FilterResolved::Sub(attr, subvalue, _) => {
self.attribute_substring(attr.as_str(), subvalue)
}
FilterResolved::Pres(attr, _) => {
self.attribute_pres(attr.as_str())
}
FilterResolved::LessThan(attr, subvalue, _) => {
self.attribute_lessthan(attr.as_str(), subvalue)
}
FilterResolved::Or(l) => l.iter().fold(false, |acc, f| {
if acc {
acc
} else {
self.entry_match_no_index_inner(f)
}
}),
FilterResolved::And(l) => l.iter().fold(true, |acc, f| {
if acc {
self.entry_match_no_index_inner(f)
} else {
acc
}
}),
FilterResolved::Inclusion(_) => {
false
}
FilterResolved::AndNot(f) => !self.entry_match_no_index_inner(f),
}
}
pub fn filter_from_attrs(&self, attrs: &[String]) -> Option<Filter<FilterInvalid>> {
let mut pairs: Vec<(&str, &Value)> = Vec::new();
for attr in attrs {
match self.attrs.get(attr) {
Some(values) => {
for v in values {
pairs.push((attr, v))
}
}
None => return None,
}
}
Some(filter_all!(f_and(
pairs
.into_iter()
.map(|(attr, value)| {
FC::Eq(attr, value.to_partialvalue())
})
.collect()
)))
}
pub fn gen_modlist_assert(
&self,
schema: &dyn SchemaTransaction,
) -> Result<ModifyList<ModifyInvalid>, SchemaError> {
let mut mods = ModifyList::new();
for (k, vs) in self.attrs.iter() {
if k == "uuid" {
continue;
}
match schema.is_multivalue(k) {
Ok(r) => {
if !r {
mods.push_mod(Modify::Purged(k.clone()));
}
}
Err(e) => return Err(e),
}
for v in vs {
mods.push_mod(Modify::Present(k.clone(), v.clone()));
}
}
Ok(mods)
}
}
impl<STATE> Entry<EntryInvalid, STATE>
where
STATE: Clone,
{
pub fn add_ava(&mut self, attr: &str, value: Value) {
self.add_ava_int(attr, value)
}
fn remove_ava(&mut self, attr: &str, value: &PartialValue) {
self.attrs.entry(attr.to_string()).and_modify(|v| {
v.remove(value);
});
}
pub(crate) fn remove_avas(&mut self, attr: &str, values: &BTreeSet<PartialValue>) {
if let Some(set) = self.attrs.get_mut(attr) {
values.iter().for_each(|k| {
set.remove(k);
})
}
}
pub fn purge_ava(&mut self, attr: &str) {
self.attrs.remove(attr);
}
pub fn pop_ava(&mut self, attr: &str) -> Option<Set<Value>> {
self.attrs.remove(attr)
}
pub fn set_ava(&mut self, attr: &str, values: Set<Value>) {
let _ = self.attrs.insert(attr.to_string(), values);
}
pub fn apply_modlist(&mut self, modlist: &ModifyList<ModifyValid>) {
for modify in modlist {
match modify {
Modify::Present(a, v) => self.add_ava(a.as_str(), v.clone()),
Modify::Removed(a, v) => self.remove_ava(a.as_str(), v),
Modify::Purged(a) => self.purge_ava(a.as_str()),
}
}
}
}
impl<VALID, STATE> PartialEq for Entry<VALID, STATE> {
fn eq(&self, rhs: &Entry<VALID, STATE>) -> bool {
compare_attrs(&self.attrs, &rhs.attrs)
}
}
impl From<&SchemaAttribute> for Entry<EntryInit, EntryNew> {
fn from(s: &SchemaAttribute) -> Self {
let uuid_v = btreeset![Value::new_uuidr(&s.uuid)];
let name_v = btreeset![Value::new_iutf8(s.name.clone())];
let desc_v = btreeset![Value::new_utf8(s.description.clone())];
let multivalue_v = btreeset![Value::from(s.multivalue)];
let unique_v = btreeset![Value::from(s.unique)];
let index_v: Set<_> = s.index.iter().map(|i| Value::from(i.clone())).collect();
let syntax_v = btreeset![Value::from(s.syntax.clone())];
let mut attrs: Map<String, Set<Value>> = Map::with_capacity(16);
attrs.insert("attributename".to_string(), name_v);
attrs.insert("description".to_string(), desc_v);
attrs.insert("uuid".to_string(), uuid_v);
attrs.insert("multivalue".to_string(), multivalue_v);
attrs.insert("unique".to_string(), unique_v);
attrs.insert("index".to_string(), index_v);
attrs.insert("syntax".to_string(), syntax_v);
attrs.insert(
"class".to_string(),
btreeset![
Value::new_class("object"),
Value::new_class("system"),
Value::new_class("attributetype")
],
);
Entry {
valid: EntryInit,
state: EntryNew,
attrs,
}
}
}
impl From<&SchemaClass> for Entry<EntryInit, EntryNew> {
fn from(s: &SchemaClass) -> Self {
let uuid_v = btreeset![Value::new_uuidr(&s.uuid)];
let name_v = btreeset![Value::new_iutf8(s.name.clone())];
let desc_v = btreeset![Value::new_utf8(s.description.clone())];
let mut attrs: Map<String, Set<Value>> = Map::with_capacity(16);
attrs.insert("classname".to_string(), name_v);
attrs.insert("description".to_string(), desc_v);
attrs.insert("uuid".to_string(), uuid_v);
attrs.insert(
"class".to_string(),
btreeset![
Value::new_class("object"),
Value::new_class("system"),
Value::new_class("classtype")
],
);
if !s.systemmay.is_empty() {
attrs.insert(
"systemmay".to_string(),
s.systemmay
.iter()
.map(|sm| Value::new_attr(sm.as_str()))
.collect(),
);
}
if !s.systemmust.is_empty() {
attrs.insert(
"systemmust".to_string(),
s.systemmust
.iter()
.map(|sm| Value::new_attr(sm.as_str()))
.collect(),
);
}
Entry {
valid: EntryInit,
state: EntryNew,
attrs,
}
}
}
#[cfg(test)]
mod tests {
use crate::be::IdxKey;
use crate::entry::{Entry, EntryInit, EntryInvalid, EntryNew};
use crate::modify::{Modify, ModifyList};
use crate::value::{IndexType, PartialValue, Value};
use hashbrown::HashSet;
use std::collections::BTreeSet as Set;
#[test]
fn test_entry_basic() {
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("userid", Value::from("william"));
}
#[test]
fn test_entry_dup_value() {
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("userid", Value::from("william"));
e.add_ava("userid", Value::from("william"));
let values = e.get_ava_set("userid").expect("Failed to get ava");
assert_eq!(values.len(), 1)
}
#[test]
fn test_entry_pres() {
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("userid", Value::from("william"));
assert!(e.attribute_pres("userid"));
assert!(!e.attribute_pres("name"));
}
#[test]
fn test_entry_equality() {
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("userid", Value::from("william"));
assert!(e.attribute_equality("userid", &PartialValue::new_utf8s("william")));
assert!(!e.attribute_equality("userid", &PartialValue::new_utf8s("test")));
assert!(!e.attribute_equality("nonexist", &PartialValue::new_utf8s("william")));
assert!(!e.attribute_equality("userid", &PartialValue::new_class("william")));
}
#[test]
fn test_entry_substring() {
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("userid", Value::from("william"));
assert!(e.attribute_substring("userid", &PartialValue::new_utf8s("william")));
assert!(e.attribute_substring("userid", &PartialValue::new_utf8s("will")));
assert!(e.attribute_substring("userid", &PartialValue::new_utf8s("liam")));
assert!(e.attribute_substring("userid", &PartialValue::new_utf8s("lli")));
assert!(!e.attribute_substring("userid", &PartialValue::new_utf8s("llim")));
assert!(!e.attribute_substring("userid", &PartialValue::new_utf8s("bob")));
assert!(!e.attribute_substring("userid", &PartialValue::new_utf8s("wl")));
}
#[test]
fn test_entry_lessthan() {
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
let pv2 = PartialValue::new_uint32(2);
let pv8 = PartialValue::new_uint32(8);
let pv10 = PartialValue::new_uint32(10);
let pv15 = PartialValue::new_uint32(15);
e1.add_ava("a", Value::new_uint32(10));
assert!(e1.attribute_lessthan("a", &pv2) == false);
assert!(e1.attribute_lessthan("a", &pv8) == false);
assert!(e1.attribute_lessthan("a", &pv10) == false);
assert!(e1.attribute_lessthan("a", &pv15) == true);
e1.add_ava("a", Value::new_uint32(8));
assert!(e1.attribute_lessthan("a", &pv2) == false);
assert!(e1.attribute_lessthan("a", &pv8) == false);
assert!(e1.attribute_lessthan("a", &pv10) == true);
assert!(e1.attribute_lessthan("a", &pv15) == true);
}
#[test]
fn test_entry_apply_modlist() {
let mut e: Entry<EntryInvalid, EntryNew> = unsafe { Entry::new().into_invalid_new() };
e.add_ava("userid", Value::from("william"));
let present_single_mods = unsafe {
ModifyList::new_valid_list(vec![Modify::Present(
String::from("attr"),
Value::new_iutf8s("value"),
)])
};
e.apply_modlist(&present_single_mods);
assert!(e.attribute_equality("userid", &PartialValue::new_utf8s("william")));
assert!(e.attribute_equality("attr", &PartialValue::new_iutf8s("value")));
let present_multivalue_mods = unsafe {
ModifyList::new_valid_list(vec![
Modify::Present(String::from("class"), Value::new_iutf8s("test")),
Modify::Present(String::from("class"), Value::new_iutf8s("multi_test")),
])
};
e.apply_modlist(&present_multivalue_mods);
assert!(e.attribute_equality("class", &PartialValue::new_iutf8s("test")));
assert!(e.attribute_equality("class", &PartialValue::new_iutf8s("multi_test")));
let purge_single_mods =
unsafe { ModifyList::new_valid_list(vec![Modify::Purged(String::from("attr"))]) };
e.apply_modlist(&purge_single_mods);
assert!(!e.attribute_pres("attr"));
let purge_multi_mods =
unsafe { ModifyList::new_valid_list(vec![Modify::Purged(String::from("class"))]) };
e.apply_modlist(&purge_multi_mods);
assert!(!e.attribute_pres("class"));
let purge_empty_mods = purge_single_mods;
e.apply_modlist(&purge_empty_mods);
let remove_mods = unsafe {
ModifyList::new_valid_list(vec![Modify::Removed(
String::from("attr"),
PartialValue::new_iutf8s("value"),
)])
};
e.apply_modlist(&present_single_mods);
assert!(e.attribute_equality("attr", &PartialValue::new_iutf8s("value")));
e.apply_modlist(&remove_mods);
assert!(e.attrs.get("attr").unwrap().is_empty());
let remove_empty_mods = remove_mods;
e.apply_modlist(&remove_empty_mods);
assert!(e.attrs.get("attr").unwrap().is_empty());
}
#[test]
fn test_entry_idx_diff() {
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava("userid", Value::from("william"));
let mut e1_mod = e1.clone();
e1_mod.add_ava("extra", Value::from("test"));
let e1 = unsafe { e1.into_sealed_committed() };
let e1_mod = unsafe { e1_mod.into_sealed_committed() };
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
e2.add_ava("userid", Value::from("claire"));
let e2 = unsafe { e2.into_sealed_committed() };
let mut idxmeta = HashSet::with_capacity(8);
idxmeta.insert(IdxKey {
attr: "userid".to_string(),
itype: IndexType::EQUALITY,
});
idxmeta.insert(IdxKey {
attr: "userid".to_string(),
itype: IndexType::PRESENCE,
});
idxmeta.insert(IdxKey {
attr: "extra".to_string(),
itype: IndexType::EQUALITY,
});
let r1 = Entry::idx_diff(&idxmeta, None, None);
eprintln!("{:?}", r1);
assert!(r1 == Vec::new());
let mut del_r = Entry::idx_diff(&idxmeta, Some(&e1), None);
del_r.sort_unstable();
eprintln!("del_r {:?}", del_r);
assert!(
del_r[0]
== Err((
&"userid".to_string(),
&IndexType::EQUALITY,
"william".to_string()
))
);
assert!(del_r[1] == Err((&"userid".to_string(), &IndexType::PRESENCE, "_".to_string())));
let mut add_r = Entry::idx_diff(&idxmeta, None, Some(&e1));
add_r.sort_unstable();
eprintln!("{:?}", add_r);
assert!(
add_r[0]
== Ok((
&"userid".to_string(),
&IndexType::EQUALITY,
"william".to_string()
))
);
assert!(add_r[1] == Ok((&"userid".to_string(), &IndexType::PRESENCE, "_".to_string())));
let no_r = Entry::idx_diff(&idxmeta, Some(&e1), Some(&e1));
assert!(no_r.len() == 0);
let add_a_r = Entry::idx_diff(&idxmeta, Some(&e1), Some(&e1_mod));
assert!(
add_a_r[0]
== Ok((
&"extra".to_string(),
&IndexType::EQUALITY,
"test".to_string()
))
);
let del_a_r = Entry::idx_diff(&idxmeta, Some(&e1_mod), Some(&e1));
assert!(
del_a_r[0]
== Err((
&"extra".to_string(),
&IndexType::EQUALITY,
"test".to_string()
))
);
let mut chg_r = Entry::idx_diff(&idxmeta, Some(&e1), Some(&e2));
chg_r.sort_unstable();
eprintln!("{:?}", chg_r);
assert!(
chg_r[1]
== Err((
&"userid".to_string(),
&IndexType::EQUALITY,
"william".to_string()
))
);
assert!(
chg_r[0]
== Ok((
&"userid".to_string(),
&IndexType::EQUALITY,
"claire".to_string()
))
);
}
#[test]
fn test_entry_mask_recycled_ts() {
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava("class", Value::new_class("person"));
let e1 = unsafe { e1.into_sealed_committed() };
assert!(e1.mask_recycled_ts().is_some());
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
e2.add_ava("class", Value::new_class("person"));
e2.add_ava("class", Value::new_class("recycled"));
let e2 = unsafe { e2.into_sealed_committed() };
assert!(e2.mask_recycled_ts().is_none());
let mut e3: Entry<EntryInit, EntryNew> = Entry::new();
e3.add_ava("class", Value::new_class("tombstone"));
let e3 = unsafe { e3.into_sealed_committed() };
assert!(e3.mask_recycled_ts().is_none());
}
#[test]
fn test_entry_idx_name2uuid_diff() {
let r = Entry::idx_name2uuid_diff(None, None);
assert!(r == (None, None));
{
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("class", Value::new_class("person"));
let e = unsafe { e.into_sealed_committed() };
assert!(Entry::idx_name2uuid_diff(None, Some(&e)) == (Some(Set::new()), None));
}
{
let mut e: Entry<EntryInit, EntryNew> = Entry::new();
e.add_ava("class", Value::new_class("person"));
e.add_ava("gidnumber", Value::new_uint32(1300));
e.add_ava("name", Value::new_iname_s("testperson"));
e.add_ava("spn", Value::new_spn_str("testperson", "example.com"));
e.add_ava(
"uuid",
Value::new_uuids("9fec0398-c46c-4df4-9df5-b0016f7d563f").unwrap(),
);
let e = unsafe { e.into_sealed_committed() };
assert!(
Entry::idx_name2uuid_diff(None, Some(&e))
== (
Some(btreeset![
"1300".to_string(),
"testperson".to_string(),
"testperson@example.com".to_string()
]),
None
)
);
assert!(
Entry::idx_name2uuid_diff(Some(&e), None)
== (
None,
Some(btreeset![
"1300".to_string(),
"testperson".to_string(),
"testperson@example.com".to_string()
])
)
);
assert!(
Entry::idx_name2uuid_diff(Some(&e), Some(&e))
== (Some(Set::new()), Some(Set::new()))
);
}
{
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava("class", Value::new_class("person"));
e1.add_ava("spn", Value::new_spn_str("testperson", "example.com"));
let e1 = unsafe { e1.into_sealed_committed() };
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
e2.add_ava("class", Value::new_class("person"));
e2.add_ava("name", Value::new_iname_s("testperson"));
e2.add_ava("spn", Value::new_spn_str("testperson", "example.com"));
let e2 = unsafe { e2.into_sealed_committed() };
assert!(
Entry::idx_name2uuid_diff(Some(&e1), Some(&e2))
== (Some(btreeset!["testperson".to_string()]), Some(Set::new()))
);
assert!(
Entry::idx_name2uuid_diff(Some(&e2), Some(&e1))
== (Some(Set::new()), Some(btreeset!["testperson".to_string()]))
);
}
{
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava("class", Value::new_class("person"));
e1.add_ava("spn", Value::new_spn_str("testperson", "example.com"));
let e1 = unsafe { e1.into_sealed_committed() };
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
e2.add_ava("class", Value::new_class("person"));
e2.add_ava("spn", Value::new_spn_str("renameperson", "example.com"));
let e2 = unsafe { e2.into_sealed_committed() };
assert!(
Entry::idx_name2uuid_diff(Some(&e1), Some(&e2))
== (
Some(btreeset!["renameperson@example.com".to_string()]),
Some(btreeset!["testperson@example.com".to_string()])
)
);
}
}
#[test]
fn test_entry_idx_uuid2spn_diff() {
assert!(Entry::idx_uuid2spn_diff(None, None) == None);
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava("spn", Value::new_spn_str("testperson", "example.com"));
let e1 = unsafe { e1.into_sealed_committed() };
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
e2.add_ava("spn", Value::new_spn_str("renameperson", "example.com"));
let e2 = unsafe { e2.into_sealed_committed() };
assert!(
Entry::idx_uuid2spn_diff(None, Some(&e1))
== Some(Ok(Value::new_spn_str("testperson", "example.com")))
);
assert!(Entry::idx_uuid2spn_diff(Some(&e1), None) == Some(Err(())));
assert!(Entry::idx_uuid2spn_diff(Some(&e1), Some(&e1)) == None);
assert!(
Entry::idx_uuid2spn_diff(Some(&e1), Some(&e2))
== Some(Ok(Value::new_spn_str("renameperson", "example.com")))
);
}
#[test]
fn test_entry_idx_uuid2rdn_diff() {
assert!(Entry::idx_uuid2rdn_diff(None, None) == None);
let mut e1: Entry<EntryInit, EntryNew> = Entry::new();
e1.add_ava("spn", Value::new_spn_str("testperson", "example.com"));
let e1 = unsafe { e1.into_sealed_committed() };
let mut e2: Entry<EntryInit, EntryNew> = Entry::new();
e2.add_ava("spn", Value::new_spn_str("renameperson", "example.com"));
let e2 = unsafe { e2.into_sealed_committed() };
assert!(
Entry::idx_uuid2rdn_diff(None, Some(&e1))
== Some(Ok("spn=testperson@example.com".to_string()))
);
assert!(Entry::idx_uuid2rdn_diff(Some(&e1), None) == Some(Err(())));
assert!(Entry::idx_uuid2rdn_diff(Some(&e1), Some(&e1)) == None);
assert!(
Entry::idx_uuid2rdn_diff(Some(&e1), Some(&e2))
== Some(Ok("spn=renameperson@example.com".to_string()))
);
}
}