use hashbrown::HashMap;
use std::cell::Cell;
use std::collections::BTreeSet;
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
use crate::audit::AuditScope;
use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction};
use crate::access::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
AccessControls, AccessControlsReadTransaction, AccessControlsTransaction,
AccessControlsWriteTransaction,
};
use crate::constants::*;
use crate::entry::{
Entry, EntryCommitted, EntryInit, EntryInvalid, EntryNew, EntryReduced, EntrySealed,
};
use crate::event::{
CreateEvent, DeleteEvent, Event, EventOrigin, ExistsEvent, ModifyEvent, ReviveRecycledEvent,
SearchEvent,
};
use crate::filter::{Filter, FilterInvalid, FilterValid};
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
use crate::plugins::Plugins;
use crate::repl::cid::Cid;
use crate::schema::{
Schema, SchemaAttribute, SchemaClass, SchemaReadTransaction, SchemaTransaction,
SchemaWriteTransaction,
};
use crate::value::{PartialValue, SyntaxType, Value};
use kanidm_proto::v1::{ConsistencyError, OperationError, SchemaError};
type EntrySealedCommitted = Entry<EntrySealed, EntryCommitted>;
type EntryInvalidCommitted = Entry<EntryInvalid, EntryCommitted>;
type EntryTuple = (EntrySealedCommitted, EntryInvalidCommitted);
lazy_static! {
static ref PVCLASS_ATTRIBUTETYPE: PartialValue = PartialValue::new_class("attributetype");
static ref PVCLASS_CLASSTYPE: PartialValue = PartialValue::new_class("classtype");
static ref PVCLASS_TOMBSTONE: PartialValue = PartialValue::new_class("tombstone");
static ref PVCLASS_RECYCLED: PartialValue = PartialValue::new_class("recycled");
static ref PVCLASS_ACS: PartialValue = PartialValue::new_class("access_control_search");
static ref PVCLASS_ACD: PartialValue = PartialValue::new_class("access_control_delete");
static ref PVCLASS_ACM: PartialValue = PartialValue::new_class("access_control_modify");
static ref PVCLASS_ACC: PartialValue = PartialValue::new_class("access_control_create");
static ref PVCLASS_ACP: PartialValue = PartialValue::new_class("access_control_profile");
static ref PVACP_ENABLE_FALSE: PartialValue = PartialValue::new_bool(false);
}
pub trait QueryServerTransaction {
type BackendTransactionType: BackendTransaction;
fn get_be_txn(&self) -> &Self::BackendTransactionType;
type SchemaTransactionType: SchemaTransaction;
fn get_schema(&self) -> &Self::SchemaTransactionType;
type AccessControlsTransactionType: AccessControlsTransaction;
fn get_accesscontrols(&self) -> &Self::AccessControlsTransactionType;
fn search_ext(
&self,
au: &mut AuditScope,
se: &SearchEvent,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
lperf_segment!(au, "server::search_ext", || {
let entries = self.search(au, se)?;
let access = self.get_accesscontrols();
access
.search_filter_entry_attributes(au, se, entries)
.map_err(|e| {
ladmin_error!(au, "Failed to filter entry attributes {:?}", e);
e
})
})
}
fn search(
&self,
au: &mut AuditScope,
se: &SearchEvent,
) -> Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> {
lperf_segment!(au, "server::search", || {
if se.event.is_internal() {
ltrace!(au, "search: internal filter -> {:?}", se.filter);
} else {
lsecurity!(au, "search initiator: -> {}", se.event);
ladmin_info!(au, "search: external filter -> {:?}", se.filter);
}
let be_txn = self.get_be_txn();
let idxmeta = be_txn.get_idxmeta_ref();
let vfr = lperf_trace_segment!(au, "server::search<filter_resolve>", || {
se.filter.resolve(&se.event, Some(idxmeta))
})
.map_err(|e| {
ladmin_error!(au, "search filter resolve failure {:?}", e);
e
})?;
let res = self.get_be_txn().search(au, &vfr).map(|r| r).map_err(|e| {
ladmin_error!(au, "backend failure -> {:?}", e);
OperationError::Backend
})?;
let access = self.get_accesscontrols();
access.search_filter_entries(au, se, res).map_err(|e| {
ladmin_error!(au, "Unable to access filter entries {:?}", e);
e
})
})
}
fn exists(&self, au: &mut AuditScope, ee: &ExistsEvent) -> Result<bool, OperationError> {
lperf_segment!(au, "server::exists", || {
let be_txn = self.get_be_txn();
let idxmeta = be_txn.get_idxmeta_ref();
let vfr = ee.filter.resolve(&ee.event, Some(idxmeta)).map_err(|e| {
ladmin_error!(au, "Failed to resolve filter {:?}", e);
e
})?;
self.get_be_txn().exists(au, &vfr).map_err(|e| {
ladmin_error!(au, "backend failure -> {:?}", e);
OperationError::Backend
})
})
}
fn name_to_uuid(&self, audit: &mut AuditScope, name: &str) -> Result<Uuid, OperationError> {
Uuid::parse_str(name).or_else(|_| {
let lname = name.to_lowercase();
self.get_be_txn()
.name2uuid(audit, lname.as_str())?
.ok_or(OperationError::NoMatchingEntries)
})
}
fn uuid_to_spn(
&self,
audit: &mut AuditScope,
uuid: &Uuid,
) -> Result<Option<Value>, OperationError> {
let r = self.get_be_txn().uuid2spn(audit, uuid)?;
match &r {
Some(n) => {
debug_assert!(n.is_spn() || n.is_iname());
}
None => {}
}
Ok(r)
}
fn uuid_to_rdn(&self, audit: &mut AuditScope, uuid: &Uuid) -> Result<String, OperationError> {
self.get_be_txn()
.uuid2rdn(audit, uuid)
.and_then(|v| match v {
Some(u) => Ok(u),
None => Ok(format!("uuid={}", uuid.to_hyphenated_ref())),
})
}
fn internal_exists(
&self,
au: &mut AuditScope,
filter: Filter<FilterInvalid>,
) -> Result<bool, OperationError> {
lperf_segment!(au, "server::internal_exists", || {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let ee = ExistsEvent::new_internal(f_valid);
self.exists(au, &ee)
})
}
fn internal_search(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
) -> Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> {
lperf_segment!(audit, "server::internal_search", || {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let se = SearchEvent::new_internal(f_valid);
self.search(audit, &se)
})
}
fn impersonate_search_valid(
&self,
audit: &mut AuditScope,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
event: &Event,
) -> Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> {
lperf_segment!(audit, "server::internal_search_valid", || {
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
self.search(audit, &se)
})
}
fn impersonate_search_ext_valid(
&self,
audit: &mut AuditScope,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
event: &Event,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
self.search_ext(audit, &se)
}
fn impersonate_search_ext(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
filter_intent: Filter<FilterInvalid>,
event: &Event,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
lperf_segment!(audit, "server::internal_search_ext_valid", || {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let f_intent_valid = filter_intent
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
self.impersonate_search_ext_valid(audit, f_valid, f_intent_valid, event)
})
}
fn internal_search_uuid(
&self,
audit: &mut AuditScope,
uuid: &Uuid,
) -> Result<Entry<EntrySealed, EntryCommitted>, OperationError> {
lperf_segment!(audit, "server::internal_search_uuid", || {
let filter = filter!(f_eq("uuid", PartialValue::new_uuid(*uuid)));
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let se = SearchEvent::new_internal(f_valid);
let res = self.search(audit, &se);
match res {
Ok(vs) => {
if vs.len() > 1 {
return Err(OperationError::NoMatchingEntries);
}
vs.into_iter()
.next()
.ok_or(OperationError::NoMatchingEntries)
}
Err(e) => Err(e),
}
})
}
fn impersonate_search_ext_uuid(
&self,
audit: &mut AuditScope,
uuid: &Uuid,
event: &Event,
) -> Result<Entry<EntryReduced, EntryCommitted>, OperationError> {
lperf_segment!(audit, "server::internal_search_ext_uuid", || {
let filter_intent = filter_all!(f_eq("uuid", PartialValue::new_uuid(*uuid)));
let filter = filter!(f_eq("uuid", PartialValue::new_uuid(*uuid)));
let res = self.impersonate_search_ext(audit, filter, filter_intent, event);
match res {
Ok(vs) => {
if vs.len() > 1 {
return Err(OperationError::NoMatchingEntries);
}
vs.into_iter()
.next()
.ok_or(OperationError::NoMatchingEntries)
}
Err(e) => Err(e),
}
})
}
fn clone_value(
&self,
audit: &mut AuditScope,
attr: &str,
value: &str,
) -> Result<Value, OperationError> {
let schema = self.get_schema();
match schema.get_attributes().get(attr) {
Some(schema_a) => {
match schema_a.syntax {
SyntaxType::UTF8STRING => Ok(Value::new_utf8(value.to_string())),
SyntaxType::UTF8STRING_INSENSITIVE => Ok(Value::new_iutf8s(value)),
SyntaxType::UTF8STRING_INAME => Ok(Value::new_iname_s(value)),
SyntaxType::BOOLEAN => Value::new_bools(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
SyntaxType::SYNTAX_ID => Value::new_syntaxs(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
SyntaxType::INDEX_ID => Value::new_indexs(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
SyntaxType::UUID => {
Value::new_uuids(value)
.or_else(|| {
let un = self
.name_to_uuid(audit, value)
.unwrap_or_else(|_| *UUID_DOES_NOT_EXIST);
Some(Value::new_uuid(un))
})
.ok_or_else(|| OperationError::InvalidAttribute("Invalid UUID syntax".to_string()))
}
SyntaxType::REFERENCE_UUID => {
Value::new_refer_s(value)
.or_else(|| {
let un = self
.name_to_uuid(audit, value)
.unwrap_or_else(|_| *UUID_DOES_NOT_EXIST);
Some(Value::new_refer(un))
})
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Reference syntax".to_string()))
}
SyntaxType::JSON_FILTER => Value::new_json_filter(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
SyntaxType::CREDENTIAL => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::RADIUS_UTF8STRING => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SSHKEY => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SERVICE_PRINCIPLE_NAME => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
SyntaxType::UINT32 => Value::new_uint32_str(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
SyntaxType::CID => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
SyntaxType::NSUNIQUEID => Ok(Value::new_nsuniqueid_s(value)),
}
}
None => {
Err(OperationError::InvalidAttributeName(attr.to_string()))
}
}
}
fn clone_partialvalue(
&self,
audit: &mut AuditScope,
attr: &str,
value: &str,
) -> Result<PartialValue, OperationError> {
let schema = self.get_schema();
match schema.get_attributes().get(attr) {
Some(schema_a) => {
match schema_a.syntax {
SyntaxType::UTF8STRING => Ok(PartialValue::new_utf8(value.to_string())),
SyntaxType::UTF8STRING_INSENSITIVE => Ok(PartialValue::new_iutf8s(value)),
SyntaxType::UTF8STRING_INAME => Ok(PartialValue::new_iname(value)),
SyntaxType::BOOLEAN => PartialValue::new_bools(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
}),
SyntaxType::SYNTAX_ID => PartialValue::new_syntaxs(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
}),
SyntaxType::INDEX_ID => PartialValue::new_indexs(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Index syntax".to_string())
}),
SyntaxType::UUID => {
PartialValue::new_uuids(value)
.or_else(|| {
let un = self
.name_to_uuid(audit, value)
.unwrap_or_else(|_| *UUID_DOES_NOT_EXIST);
Some(PartialValue::new_uuid(un))
})
.ok_or_else(|| {
OperationError::InvalidAttribute("Invalid UUID syntax".to_string())
})
}
SyntaxType::REFERENCE_UUID => {
PartialValue::new_refer_s(value)
.or_else(|| {
let un = self
.name_to_uuid(audit, value)
.unwrap_or_else(|_| *UUID_DOES_NOT_EXIST);
Some(PartialValue::new_refer(un))
})
.ok_or_else(|| {
OperationError::InvalidAttribute(
"Invalid Reference syntax".to_string(),
)
})
}
SyntaxType::JSON_FILTER => {
PartialValue::new_json_filter(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
})
}
SyntaxType::CREDENTIAL => Ok(PartialValue::new_credential_tag(value)),
SyntaxType::RADIUS_UTF8STRING => Ok(PartialValue::new_radius_string()),
SyntaxType::SSHKEY => Ok(PartialValue::new_sshkey_tag_s(value)),
SyntaxType::SERVICE_PRINCIPLE_NAME => PartialValue::new_spn_s(value)
.ok_or_else(|| {
OperationError::InvalidAttribute("Invalid spn syntax".to_string())
}),
SyntaxType::UINT32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
}),
SyntaxType::CID => PartialValue::new_cid_s(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid cid syntax".to_string())
}),
SyntaxType::NSUNIQUEID => Ok(PartialValue::new_nsuniqueid_s(value)),
}
}
None => {
Err(OperationError::InvalidAttributeName(attr.to_string()))
}
}
}
fn resolve_value(
&self,
audit: &mut AuditScope,
value: &Value,
) -> Result<String, OperationError> {
if let Some(ur) = value.to_ref_uuid() {
let nv = self.uuid_to_spn(audit, ur)?;
return match nv {
Some(v) => Ok(v.to_proto_string_clone()),
None => Ok(value.to_proto_string_clone()),
};
}
Ok(value.to_proto_string_clone())
}
fn resolve_value_ldap(
&self,
audit: &mut AuditScope,
value: &Value,
basedn: &str,
) -> Result<String, OperationError> {
if let Some(ur) = value.to_ref_uuid() {
let rdn = self.uuid_to_rdn(audit, ur)?;
Ok(format!("{},{}", rdn, basedn))
} else if value.is_sshkey() {
value
.get_sshkey()
.map(|s| s.to_string())
.ok_or_else(|| OperationError::InvalidValueState)
} else {
Ok(value.to_proto_string_clone())
}
}
}
pub struct QueryServerReadTransaction<'a> {
be_txn: BackendReadTransaction<'a>,
schema: SchemaReadTransaction,
accesscontrols: AccessControlsReadTransaction,
}
impl<'a> QueryServerTransaction for QueryServerReadTransaction<'a> {
type BackendTransactionType = BackendReadTransaction<'a>;
fn get_be_txn(&self) -> &BackendReadTransaction<'a> {
&self.be_txn
}
type SchemaTransactionType = SchemaReadTransaction;
fn get_schema(&self) -> &SchemaReadTransaction {
&self.schema
}
type AccessControlsTransactionType = AccessControlsReadTransaction;
fn get_accesscontrols(&self) -> &AccessControlsReadTransaction {
&self.accesscontrols
}
}
impl<'a> QueryServerReadTransaction<'a> {
fn verify(&self, au: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
let be_errs = self.get_be_txn().verify();
if !be_errs.is_empty() {
return be_errs;
}
let sc_errs = self.get_schema().validate(au);
if !sc_errs.is_empty() {
return sc_errs;
}
Plugins::run_verify(au, self)
}
}
pub struct QueryServerWriteTransaction<'a> {
committed: bool,
d_uuid: Uuid,
cid: Cid,
be_txn: BackendWriteTransaction<'a>,
schema: SchemaWriteTransaction<'a>,
accesscontrols: AccessControlsWriteTransaction<'a>,
changed_schema: Cell<bool>,
changed_acp: Cell<bool>,
}
impl<'a> QueryServerTransaction for QueryServerWriteTransaction<'a> {
type BackendTransactionType = BackendWriteTransaction<'a>;
fn get_be_txn(&self) -> &BackendWriteTransaction<'a> {
&self.be_txn
}
type SchemaTransactionType = SchemaWriteTransaction<'a>;
fn get_schema(&self) -> &SchemaWriteTransaction<'a> {
&self.schema
}
type AccessControlsTransactionType = AccessControlsWriteTransaction<'a>;
fn get_accesscontrols(&self) -> &AccessControlsWriteTransaction<'a> {
&self.accesscontrols
}
}
#[derive(Clone, Debug)]
struct QueryServerMeta {
pub max_cid: Cid,
}
#[derive(Clone)]
pub struct QueryServer {
s_uuid: Uuid,
d_uuid: Uuid,
be: Backend,
schema: Arc<Schema>,
accesscontrols: Arc<AccessControls>,
}
impl QueryServer {
pub fn new(be: Backend, schema: Schema) -> Self {
let (s_uuid, d_uuid) = {
let wr = be.write();
(wr.get_db_s_uuid(), wr.get_db_d_uuid())
};
info!("Server ID -> {:?}", s_uuid);
info!("Domain ID -> {:?}", d_uuid);
QueryServer {
s_uuid,
d_uuid,
be,
schema: Arc::new(schema),
accesscontrols: Arc::new(AccessControls::new()),
}
}
pub fn read(&self) -> QueryServerReadTransaction {
QueryServerReadTransaction {
be_txn: self.be.read(),
schema: self.schema.read(),
accesscontrols: self.accesscontrols.read(),
}
}
pub fn write(&self, ts: Duration) -> QueryServerWriteTransaction {
let schema_write = self.schema.write();
let be_txn = self.be.write();
let ts_max = be_txn.get_db_ts_max(&ts).expect("Unable to get db_ts_max");
let cid = Cid::new_lamport(self.s_uuid, self.d_uuid, ts, &ts_max);
QueryServerWriteTransaction {
committed: false,
d_uuid: self.d_uuid,
cid,
be_txn,
schema: schema_write,
accesscontrols: self.accesscontrols.write(),
changed_schema: Cell::new(false),
changed_acp: Cell::new(false),
}
}
pub(crate) fn initialise_helper(
&self,
audit: &mut AuditScope,
ts: Duration,
) -> Result<(), OperationError> {
let reindex_write_1 = self.write(ts);
reindex_write_1
.upgrade_reindex(audit, SYSTEM_INDEX_VERSION)
.and_then(|_| reindex_write_1.commit(audit))?;
let ts_write_1 = self.write(ts);
ts_write_1
.initialise_schema_core(audit)
.and_then(|_| ts_write_1.commit(audit))?;
let ts_write_2 = self.write(ts);
ts_write_2
.initialise_schema_idm(audit)
.and_then(|_| ts_write_2.commit(audit))?;
let reindex_write_2 = self.write(ts);
reindex_write_2
.upgrade_reindex(audit, SYSTEM_INDEX_VERSION + 1)
.and_then(|_| reindex_write_2.commit(audit))?;
let migrate_txn = self.write(ts);
let system_info_version = match migrate_txn.internal_search_uuid(audit, &UUID_SYSTEM_INFO) {
Ok(e) => Ok(e.get_ava_single_uint32("version").unwrap_or(0)),
Err(OperationError::NoMatchingEntries) => Ok(0),
Err(r) => Err(r),
}?;
ladmin_info!(audit, "current system version -> {:?}", system_info_version);
if system_info_version < 3 {
migrate_txn.migrate_2_to_3(audit)?;
}
migrate_txn.commit(audit)?;
let ts_write_3 = self.write(ts);
ts_write_3
.initialise_idm(audit)
.and_then(|_| ts_write_3.commit(audit))?;
ladmin_info!(audit, "ready to rock! 🤘");
Ok(())
}
pub fn verify(&self, au: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
let r_txn = self.read();
r_txn.verify(au)
}
}
impl<'a> QueryServerWriteTransaction<'a> {
pub fn create(&self, au: &mut AuditScope, ce: &CreateEvent) -> Result<(), OperationError> {
lperf_segment!(au, "server::create", || {
if !ce.event.is_internal() {
lsecurity!(au, "create initiator: -> {}", ce.event);
}
let candidates: Vec<Entry<EntryInit, EntryNew>> = ce.entries.clone();
let access = self.get_accesscontrols();
let op_allow = access
.create_allow_operation(au, ce, &candidates)
.map_err(|e| {
ladmin_error!(au, "Failed to check create access {:?}", e);
e
})?;
if !op_allow {
return Err(OperationError::AccessDenied);
}
let mut candidates: Vec<Entry<EntryInvalid, EntryNew>> = candidates
.into_iter()
.map(|e| e.assign_cid(self.cid.clone()))
.collect();
Plugins::run_pre_create_transform(au, self, &mut candidates, ce).map_err(|e| {
ladmin_error!(
au,
"Create operation failed (pre_transform plugin), {:?}",
e
);
e
})?;
let res: Result<Vec<Entry<EntrySealed, EntryNew>>, OperationError> = candidates
.into_iter()
.map(|e| {
e.validate(&self.schema)
.map_err(|e| {
ladmin_error!(au, "Schema Violation -> {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|e| {
e.seal()
})
})
.collect();
let norm_cand: Vec<Entry<_, _>> = res?;
Plugins::run_pre_create(au, self, &norm_cand, ce).map_err(|e| {
ladmin_error!(au, "Create operation failed (plugin), {:?}", e);
e
})?;
let commit_cand = self.be_txn.create(au, norm_cand).map_err(|e| {
ladmin_error!(au, "betxn create failure {:?}", e);
e
})?;
Plugins::run_post_create(au, self, &commit_cand, ce).map_err(|e| {
ladmin_error!(au, "Create operation failed (post plugin), {:?}", e);
e
})?;
let _ = self
.changed_schema
.replace(commit_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE)
}
}));
let _ = self
.changed_acp
.replace(commit_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_ACP)
}
}));
ltrace!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
if ce.event.is_internal() {
ltrace!(au, "Create operation success");
} else {
ladmin_info!(au, "Create operation success");
}
Ok(())
})
}
pub fn delete(&self, au: &mut AuditScope, de: &DeleteEvent) -> Result<(), OperationError> {
lperf_segment!(au, "server::delete", || {
if !de.event.is_internal() {
lsecurity!(au, "delete initiator: -> {}", de.event);
}
let pre_candidates = match self.impersonate_search_valid(
au,
de.filter.clone(),
de.filter_orig.clone(),
&de.event,
) {
Ok(results) => results,
Err(e) => {
ladmin_error!(au, "delete: error in pre-candidate selection {:?}", e);
return Err(e);
}
};
let access = self.get_accesscontrols();
let op_allow = access
.delete_allow_operation(au, de, &pre_candidates)
.map_err(|e| {
ladmin_error!(au, "Failed to check delete access {:?}", e);
e
})?;
if !op_allow {
return Err(OperationError::AccessDenied);
}
if pre_candidates.is_empty() {
lrequest_error!(au, "delete: no candidates match filter {:?}", de.filter);
return Err(OperationError::NoMatchingEntries);
};
let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
.iter()
.map(|er| er.clone().invalidate(self.cid.clone()))
.collect();
ltrace!(au, "delete: candidates -> {:?}", candidates);
Plugins::run_pre_delete(au, self, &mut candidates, de).map_err(|e| {
ladmin_error!(au, "Delete operation failed (plugin), {:?}", e);
e
})?;
ltrace!(
au,
"delete: now marking candidates as recycled -> {:?}",
candidates
);
let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
.into_iter()
.map(|e| {
e.into_recycled()
.validate(&self.schema)
.map_err(|e| {
ladmin_error!(au, "Schema Violation -> {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|r| r.seal())
})
.collect();
let del_cand: Vec<Entry<_, _>> = res?;
self.be_txn
.modify(au, &pre_candidates, &del_cand)
.map_err(|e| {
ladmin_error!(au, "Delete operation failed (backend), {:?}", e);
e
})?;
Plugins::run_post_delete(au, self, &del_cand, de).map_err(|e| {
ladmin_error!(au, "Delete operation failed (plugin), {:?}", e);
e
})?;
let _ = self
.changed_schema
.replace(del_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE)
}
}));
let _ = self
.changed_acp
.replace(del_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_ACP)
}
}));
ltrace!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
if de.event.is_internal() {
ltrace!(au, "Delete operation success");
} else {
ladmin_info!(au, "Delete operation success");
}
Ok(())
})
}
pub fn purge_tombstones(&self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_segment!(au, "server::purge_tombstones", || {
let cid = self.cid.sub_secs(CHANGELOG_MAX_AGE).map_err(|e| {
ladmin_error!(au, "Unable to generate search cid {:?}", e);
e
})?;
let ts = match self.internal_search(
au,
filter_all!(f_and!([
f_eq("class", PVCLASS_TOMBSTONE.clone()),
f_lt("last_modified_cid", PartialValue::new_cid(cid)),
])),
) {
Ok(r) => r,
Err(e) => return Err(e),
};
if ts.is_empty() {
ladmin_info!(au, "No Tombstones present - purge operation success");
return Ok(());
}
self.be_txn
.delete(au, &ts)
.map_err(|e| {
ladmin_error!(au, "Tombstone purge operation failed (backend), {:?}", e);
e
})
.map(|_| {
ladmin_info!(au, "Tombstone purge operation success");
})
})
}
pub fn purge_recycled(&self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_segment!(au, "server::purge_recycled", || {
let cid = self.cid.sub_secs(RECYCLEBIN_MAX_AGE).map_err(|e| {
ladmin_error!(au, "Unable to generate search cid {:?}", e);
e
})?;
let rc = match self.internal_search(
au,
filter_all!(f_and!([
f_eq("class", PVCLASS_RECYCLED.clone()),
f_lt("last_modified_cid", PartialValue::new_cid(cid)),
])),
) {
Ok(r) => r,
Err(e) => return Err(e),
};
if rc.is_empty() {
ladmin_info!(au, "No recycled present - purge operation success");
return Ok(());
}
let tombstone_cand: Result<Vec<_>, _> = rc
.iter()
.map(|e| {
e.to_tombstone(self.cid.clone())
.validate(&self.schema)
.map_err(|e| {
ladmin_error!(au, "Schema Violationi {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|r| r.seal())
})
.collect();
let tombstone_cand = tombstone_cand?;
self.be_txn
.modify(au, &rc, &tombstone_cand)
.map_err(|e| {
ladmin_error!(au, "Purge recycled operation failed (backend), {:?}", e);
e
})
.map(|_| {
ladmin_info!(au, "Purge recycled operation success");
})
})
}
pub fn revive_recycled(
&self,
au: &mut AuditScope,
re: &ReviveRecycledEvent,
) -> Result<(), OperationError> {
lperf_segment!(au, "server::revive_recycled", || {
let modlist = ModifyList::new_list(vec![Modify::Removed(
"class".to_string(),
PVCLASS_RECYCLED.clone(),
)]);
let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
ladmin_error!(au, "revive recycled modlist Schema Violation {:?}", e);
OperationError::SchemaViolation(e)
})?;
let revive_cands =
self.impersonate_search_valid(au, re.filter.clone(), re.filter.clone(), &re.event)?;
let mut dm_mods: HashMap<Uuid, ModifyList<ModifyInvalid>> =
HashMap::with_capacity(revive_cands.len());
revive_cands.into_iter().for_each(|e| {
let u: Uuid = *e.get_uuid();
e.get_ava_as_refuuid("directmemberof").and_then(|riter| {
riter.for_each(|g_uuid| {
dm_mods
.entry(g_uuid.clone())
.and_modify(|mlist| {
let m =
Modify::Present("member".to_string(), Value::new_refer_r(&u));
mlist.push_mod(m);
})
.or_insert({
let m =
Modify::Present("member".to_string(), Value::new_refer_r(&u));
ModifyList::new_list(vec![m])
});
});
Some(())
});
});
self.impersonate_modify_valid(
au,
re.filter.clone(),
re.filter.clone(),
m_valid,
&re.event,
)?;
let r: Result<_, _> = dm_mods
.into_iter()
.map(|(g, mods)| {
let f = filter_all!(f_eq("uuid", PartialValue::new_uuid(g)));
self.internal_modify(au, f, mods)
})
.collect();
r
})
}
pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> {
lperf_segment!(au, "server::modify", || {
if !me.event.is_internal() {
lsecurity!(au, "modify initiator: -> {}", me.event);
}
if me.modlist.len() == 0 {
lrequest_error!(au, "modify: empty modify request");
return Err(OperationError::EmptyRequest);
}
let pre_candidates = match self.impersonate_search_valid(
au,
me.filter.clone(),
me.filter_orig.clone(),
&me.event,
) {
Ok(results) => results,
Err(e) => {
ladmin_error!(au, "modify: error in pre-candidate selection {:?}", e);
return Err(e);
}
};
if pre_candidates.is_empty() {
match me.event.origin {
EventOrigin::Internal => {
ltrace!(
au,
"modify: no candidates match filter ... continuing {:?}",
me.filter
);
return Ok(());
}
_ => {
lrequest_error!(
au,
"modify: no candidates match filter, failure {:?}",
me.filter
);
return Err(OperationError::NoMatchingEntries);
}
}
};
let access = self.get_accesscontrols();
let op_allow = access
.modify_allow_operation(au, me, &pre_candidates)
.map_err(|e| {
ladmin_error!(au, "Unable to check modify access {:?}", e);
e
})?;
if !op_allow {
return Err(OperationError::AccessDenied);
}
let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
.iter()
.map(|er| er.clone().invalidate(self.cid.clone()))
.collect();
candidates
.iter_mut()
.for_each(|er| er.apply_modlist(&me.modlist));
ltrace!(au, "modify: candidates -> {:?}", candidates);
Plugins::run_pre_modify(au, self, &mut candidates, me).map_err(|e| {
ladmin_error!(au, "Modify operation failed (plugin), {:?}", e);
e
})?;
let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
.into_iter()
.map(|e| {
e.validate(&self.schema)
.map_err(|e| {
ladmin_error!(au, "Schema Violation {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|e| e.seal())
})
.collect();
let norm_cand: Vec<Entry<_, _>> = res?;
self.be_txn
.modify(au, &pre_candidates, &norm_cand)
.map_err(|e| {
ladmin_error!(au, "Modify operation failed (backend), {:?}", e);
e
})?;
Plugins::run_post_modify(au, self, &pre_candidates, &norm_cand, me).map_err(|e| {
ladmin_error!(au, "Modify operation failed (plugin), {:?}", e);
e
})?;
let _ =
self.changed_schema
.replace(norm_cand.iter().chain(pre_candidates.iter()).fold(
false,
|acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE)
}
},
));
let _ =
self.changed_acp
.replace(norm_cand.iter().chain(pre_candidates.iter()).fold(
false,
|acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_ACP)
}
},
));
ltrace!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
if me.event.is_internal() {
ltrace!(au, "Modify operation success");
} else {
ladmin_info!(au, "Modify operation success");
}
Ok(())
})
}
pub(crate) fn internal_search_writeable(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
) -> Result<Vec<EntryTuple>, OperationError> {
lperf_segment!(audit, "server::internal_search_writeable", || {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let se = SearchEvent::new_internal(f_valid);
self.search(audit, &se).map(|vs| {
vs.into_iter()
.map(|e| {
let writeable = e.clone().invalidate(self.cid.clone());
(e, writeable)
})
.collect()
})
})
}
pub(crate) fn internal_batch_modify(
&self,
au: &mut AuditScope,
pre_candidates: Vec<Entry<EntrySealed, EntryCommitted>>,
candidates: Vec<Entry<EntryInvalid, EntryCommitted>>,
) -> Result<(), OperationError> {
lperf_segment!(au, "server::internal_batch_modify", || {
lsecurity!(au, "modify initiator: -> internal batch modify");
if pre_candidates.is_empty() && candidates.is_empty() {
return Ok(());
}
if pre_candidates.len() != candidates.len() {
ladmin_error!(au, "internal_batch_modify - cand lengths differ");
return Err(OperationError::InvalidRequestState);
}
let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
.into_iter()
.map(|e| {
e.validate(&self.schema)
.map_err(|e| {
ladmin_error!(au, "Schema Violation {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|e| e.seal())
})
.collect();
let norm_cand: Vec<Entry<_, _>> = res?;
if cfg!(debug_assertions) {
pre_candidates
.iter()
.zip(norm_cand.iter())
.try_for_each(|(pre, post)| {
if pre.get_uuid() == post.get_uuid() {
Ok(())
} else {
ladmin_error!(au, "modify - cand sets not correctly aligned");
Err(OperationError::InvalidRequestState)
}
})?;
}
self.be_txn
.modify(au, &pre_candidates, &norm_cand)
.map_err(|e| {
ladmin_error!(au, "Modify operation failed (backend), {:?}", e);
e
})?;
let _ =
self.changed_schema
.replace(norm_cand.iter().chain(pre_candidates.iter()).fold(
false,
|acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE)
}
},
));
let _ =
self.changed_acp
.replace(norm_cand.iter().chain(pre_candidates.iter()).fold(
false,
|acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", &PVCLASS_ACP)
}
},
));
ltrace!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
ltrace!(au, "Modify operation success");
Ok(())
})
}
pub fn migrate_2_to_3(&self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_segment!(au, "server::migrate_2_to_3", || {
ladmin_warning!(au, "starting 2 to 3 migration. THIS MAY TAKE A LONG TIME!");
let filt = filter_all!(f_or!([f_pres("name"), f_pres("domain_name"),]));
let pre_candidates = self.internal_search(au, filt).map_err(|e| {
ladmin_error!(au, "migrate_2_to_3 internal search failure -> {:?}", e);
e
})?;
if pre_candidates.is_empty() {
ladmin_info!(au, "migrate_2_to_3 no entries to migrate, complete");
return Ok(());
}
let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
.iter()
.map(|er| er.clone().invalidate(self.cid.clone()))
.collect();
candidates.iter_mut().for_each(|er| {
let opt_names: Option<BTreeSet<_>> = er.pop_ava("name").map(|vs| {
vs.into_iter()
.filter_map(|v| v.migrate_iutf8_iname())
.collect()
});
let opt_dnames: Option<BTreeSet<_>> = er.pop_ava("domain_name").map(|vs| {
vs.into_iter()
.filter_map(|v| v.migrate_iutf8_iname())
.collect()
});
ltrace!(au, "{:?}", opt_names);
ltrace!(au, "{:?}", opt_dnames);
if let Some(v) = opt_names {
er.set_ava("name", v)
};
if let Some(v) = opt_dnames {
er.set_ava("domain_name", v)
};
});
let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, SchemaError> = candidates
.into_iter()
.map(|e| e.validate(&self.schema).map(|e| e.seal()))
.collect();
let norm_cand: Vec<Entry<_, _>> = match res {
Ok(v) => v,
Err(e) => {
ladmin_error!(au, "migrate_2_to_3 schema error -> {:?}", e);
return Err(OperationError::SchemaViolation(e));
}
};
self.be_txn
.modify(au, &pre_candidates, &norm_cand)
.map_err(|e| {
ladmin_error!(au, "migrate_2_to_3 modification failure -> {:?}", e);
e
})
})
}
pub fn internal_create(
&self,
audit: &mut AuditScope,
entries: Vec<Entry<EntryInit, EntryNew>>,
) -> Result<(), OperationError> {
let ce = CreateEvent::new_internal(entries);
self.create(audit, &ce)
}
pub fn internal_delete(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
) -> Result<(), OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let de = DeleteEvent::new_internal(f_valid);
self.delete(audit, &de)
}
pub fn internal_modify(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
) -> Result<(), OperationError> {
lperf_segment!(audit, "server::internal_modify", || {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let m_valid = modlist
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let me = ModifyEvent::new_internal(f_valid, m_valid);
self.modify(audit, &me)
})
}
pub fn impersonate_modify_valid(
&self,
audit: &mut AuditScope,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
m_valid: ModifyList<ModifyValid>,
event: &Event,
) -> Result<(), OperationError> {
let me = ModifyEvent::new_impersonate(event, f_valid, f_intent_valid, m_valid);
self.modify(audit, &me)
}
pub fn impersonate_modify(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
filter_intent: Filter<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
event: &Event,
) -> Result<(), OperationError> {
let f_valid = filter.validate(self.get_schema()).map_err(|e| {
ladmin_error!(audit, "filter Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
ladmin_error!(audit, "f_intent Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
ladmin_error!(audit, "modlist Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
self.impersonate_modify_valid(audit, f_valid, f_intent_valid, m_valid, event)
}
pub fn internal_migrate_or_create_str(
&self,
audit: &mut AuditScope,
e_str: &str,
) -> Result<(), OperationError> {
let res = lperf_segment!(audit, "server::internal_migrate_or_create_str", || {
Entry::from_proto_entry_str(audit, e_str, self)
.and_then(|e: Entry<EntryInit, EntryNew>| self.internal_migrate_or_create(audit, e))
});
ltrace!(audit, "internal_migrate_or_create_str -> result {:?}", res);
debug_assert!(res.is_ok());
res
}
pub fn internal_migrate_or_create(
&self,
audit: &mut AuditScope,
e: Entry<EntryInit, EntryNew>,
) -> Result<(), OperationError> {
ltrace!(
audit,
"internal_migrate_or_create operating on {:?}",
e.get_uuid()
);
let filt = match e.filter_from_attrs(&[String::from("uuid")]) {
Some(f) => f,
None => return Err(OperationError::FilterGeneration),
};
ltrace!(audit, "internal_migrate_or_create search {:?}", filt);
match self.internal_search(audit, filt.clone()) {
Ok(results) => {
if results.is_empty() {
self.internal_create(audit, vec![e])
} else if results.len() == 1 {
match e.gen_modlist_assert(&self.schema) {
Ok(modlist) => {
ltrace!(audit, "Generated modlist -> {:?}", modlist);
self.internal_modify(audit, filt, modlist)
}
Err(e) => Err(OperationError::SchemaViolation(e)),
}
} else {
Err(OperationError::InvalidDBState)
}
}
Err(e) => {
Err(e)
}
}
}
pub fn initialise_schema_core(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
ladmin_info!(audit, "initialise_schema_core -> start ...");
let entries = self.schema.to_entries();
let r: Result<_, _> = entries
.into_iter()
.map(|e| {
ltrace!(audit, "init schema -> {}", e);
self.internal_migrate_or_create(audit, e)
})
.collect();
if r.is_ok() {
ladmin_info!(audit, "initialise_schema_core -> Ok!");
} else {
ladmin_error!(audit, "initialise_schema_core -> Error {:?}", r);
}
debug_assert!(r.is_ok());
r
}
pub fn initialise_schema_idm(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
ladmin_info!(audit, "initialise_schema_idm -> start ...");
let idm_schema: Vec<&str> = vec![
JSON_SCHEMA_ATTR_DISPLAYNAME,
JSON_SCHEMA_ATTR_LEGALNAME,
JSON_SCHEMA_ATTR_MAIL,
JSON_SCHEMA_ATTR_SSH_PUBLICKEY,
JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
JSON_SCHEMA_ATTR_RADIUS_SECRET,
JSON_SCHEMA_ATTR_DOMAIN_NAME,
JSON_SCHEMA_ATTR_DOMAIN_UUID,
JSON_SCHEMA_ATTR_DOMAIN_SSID,
JSON_SCHEMA_ATTR_GIDNUMBER,
JSON_SCHEMA_ATTR_BADLIST_PASSWORD,
JSON_SCHEMA_ATTR_LOGINSHELL,
JSON_SCHEMA_ATTR_UNIX_PASSWORD,
JSON_SCHEMA_CLASS_PERSON,
JSON_SCHEMA_CLASS_GROUP,
JSON_SCHEMA_CLASS_ACCOUNT,
JSON_SCHEMA_CLASS_DOMAIN_INFO,
JSON_SCHEMA_CLASS_POSIXACCOUNT,
JSON_SCHEMA_CLASS_POSIXGROUP,
JSON_SCHEMA_CLASS_SYSTEM_CONFIG,
JSON_SCHEMA_ATTR_NSUNIQUEID,
];
let r: Result<Vec<()>, _> = idm_schema
.iter()
.map(|e_str| self.internal_migrate_or_create_str(audit, e_str))
.collect();
if r.is_ok() {
ladmin_info!(audit, "initialise_schema_idm -> Ok!");
} else {
ladmin_error!(audit, "initialise_schema_idm -> Error {:?}", r);
}
debug_assert!(r.is_ok());
r.map(|_| ())
}
pub fn initialise_idm(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
let res = self
.internal_migrate_or_create_str(audit, JSON_SYSTEM_INFO_V1)
.and_then(|_| self.internal_migrate_or_create_str(audit, JSON_DOMAIN_INFO_V1))
.and_then(|_| self.internal_migrate_or_create_str(audit, JSON_SYSTEM_CONFIG_V1));
if res.is_err() {
ladmin_error!(audit, "initialise_idm p1 -> result {:?}", res);
}
debug_assert!(res.is_ok());
if res.is_err() {
return res;
}
let admin_entries = [
JSON_ANONYMOUS_V1,
JSON_ADMIN_V1,
JSON_IDM_ADMIN_V1,
JSON_IDM_ADMINS_V1,
JSON_SYSTEM_ADMINS_V1,
];
let res: Result<(), _> = admin_entries
.iter()
.map(|e_str| self.internal_migrate_or_create_str(audit, e_str))
.collect();
if res.is_err() {
ladmin_error!(audit, "initialise_idm p2 -> result {:?}", res);
}
debug_assert!(res.is_ok());
if res.is_err() {
return res;
}
let idm_entries = [
JSON_IDM_PEOPLE_MANAGE_PRIV_V1,
JSON_IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1,
JSON_IDM_PEOPLE_EXTEND_PRIV_V1,
JSON_IDM_PEOPLE_WRITE_PRIV_V1,
JSON_IDM_PEOPLE_READ_PRIV_V1,
JSON_IDM_GROUP_MANAGE_PRIV_V1,
JSON_IDM_GROUP_WRITE_PRIV_V1,
JSON_IDM_GROUP_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACCOUNT_MANAGE_PRIV_V1,
JSON_IDM_ACCOUNT_WRITE_PRIV_V1,
JSON_IDM_ACCOUNT_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACCOUNT_READ_PRIV_V1,
JSON_IDM_RADIUS_SERVERS_V1,
JSON_IDM_HP_ACCOUNT_MANAGE_PRIV_V1,
JSON_IDM_HP_ACCOUNT_WRITE_PRIV_V1,
JSON_IDM_HP_ACCOUNT_READ_PRIV_V1,
JSON_IDM_SCHEMA_MANAGE_PRIV_V1,
JSON_IDM_HP_GROUP_MANAGE_PRIV_V1,
JSON_IDM_HP_GROUP_WRITE_PRIV_V1,
JSON_IDM_ACP_MANAGE_PRIV_V1,
JSON_DOMAIN_ADMINS,
JSON_IDM_HIGH_PRIVILEGE_V1,
JSON_IDM_ADMINS_ACP_RECYCLE_SEARCH_V1,
JSON_IDM_ADMINS_ACP_REVIVE_V1,
JSON_IDM_ALL_ACP_READ_V1,
JSON_IDM_SELF_ACP_READ_V1,
JSON_IDM_SELF_ACP_WRITE_V1,
JSON_IDM_ACP_PEOPLE_READ_PRIV_V1,
JSON_IDM_ACP_PEOPLE_WRITE_PRIV_V1,
JSON_IDM_ACP_PEOPLE_MANAGE_PRIV_V1,
JSON_IDM_ACP_GROUP_WRITE_PRIV_V1,
JSON_IDM_ACP_GROUP_MANAGE_PRIV_V1,
JSON_IDM_ACP_ACCOUNT_READ_PRIV_V1,
JSON_IDM_ACP_ACCOUNT_WRITE_PRIV_V1,
JSON_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1,
JSON_IDM_ACP_RADIUS_SERVERS_V1,
JSON_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1,
JSON_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1,
JSON_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1,
JSON_IDM_ACP_HP_GROUP_WRITE_PRIV_V1,
JSON_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1,
JSON_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1,
JSON_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1,
JSON_IDM_ACP_ACP_MANAGE_PRIV_V1,
JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1,
JSON_IDM_ACP_SYSTEM_CONFIG_PRIV_V1,
JSON_IDM_ACP_ACCOUNT_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACP_GROUP_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACP_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1,
JSON_IDM_ACP_PEOPLE_EXTEND_PRIV_V1,
];
let res: Result<(), _> = idm_entries
.iter()
.map(|e_str| self.internal_migrate_or_create_str(audit, e_str))
.collect();
if res.is_ok() {
ladmin_info!(audit, "initialise_idm -> result Ok!");
} else {
ladmin_error!(audit, "initialise_idm p3 -> result {:?}", res);
}
debug_assert!(res.is_ok());
if res.is_err() {
return res;
}
Ok(())
}
fn reload_schema(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
lperf_segment!(audit, "server::reload_schema", || {
ltrace!(audit, "Schema reload started ...");
let filt = filter!(f_eq("class", PVCLASS_ATTRIBUTETYPE.clone()));
let res = self.internal_search(audit, filt).map_err(|e| {
ladmin_error!(audit, "reload schema internal search failed {:?}", e);
e
})?;
let attributetypes: Result<Vec<_>, _> = res
.iter()
.map(|e| SchemaAttribute::try_from(audit, e))
.collect();
let attributetypes = attributetypes.map_err(|e| {
ladmin_error!(audit, "reload schema attributetypes {:?}", e);
e
})?;
self.schema.update_attributes(attributetypes).map_err(|e| {
ladmin_error!(audit, "reload schema update attributetypes {:?}", e);
e
})?;
let filt = filter!(f_eq("class", PVCLASS_CLASSTYPE.clone()));
let res = self.internal_search(audit, filt).map_err(|e| {
ladmin_error!(audit, "reload schema internal search failed {:?}", e);
e
})?;
let classtypes: Result<Vec<_>, _> = res
.iter()
.map(|e| SchemaClass::try_from(audit, e))
.collect();
let classtypes = classtypes.map_err(|e| {
ladmin_error!(audit, "reload schema classtypes {:?}", e);
e
})?;
self.schema.update_classes(classtypes).map_err(|e| {
ladmin_error!(audit, "reload schema update classtypes {:?}", e);
e
})?;
let valid_r = self.schema.validate(audit);
if valid_r.is_empty() {
ltrace!(audit, "Reloading idxmeta ...");
self.be_txn.update_idxmeta(self.schema.reload_idxmeta());
Ok(())
} else {
ladmin_error!(audit, "Schema reload failed -> {:?}", valid_r);
Err(OperationError::ConsistencyError(valid_r))
}
})
}
fn reload_accesscontrols(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
ltrace!(audit, "ACP reload started ...");
let filt = filter!(f_and!([
f_eq("class", PVCLASS_ACP.clone()),
f_eq("class", PVCLASS_ACS.clone()),
f_andnot(f_eq("acp_enable", PVACP_ENABLE_FALSE.clone())),
]));
let res = self.internal_search(audit, filt).map_err(|e| {
ladmin_error!(
audit,
"reload accesscontrols internal search failed {:?}",
e
);
e
})?;
let search_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlSearch::try_from(audit, self, e))
.collect();
let search_acps = search_acps.map_err(|e| {
ladmin_error!(audit, "Unable to parse search accesscontrols {:?}", e);
e
})?;
self.accesscontrols
.update_search(search_acps)
.map_err(|e| {
ladmin_error!(audit, "Failed to update search accesscontrols {:?}", e);
e
})?;
let filt = filter!(f_and!([
f_eq("class", PVCLASS_ACP.clone()),
f_eq("class", PVCLASS_ACC.clone()),
f_andnot(f_eq("acp_enable", PVACP_ENABLE_FALSE.clone())),
]));
let res = self.internal_search(audit, filt).map_err(|e| {
ladmin_error!(
audit,
"reload accesscontrols internal search failed {:?}",
e
);
e
})?;
let create_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlCreate::try_from(audit, self, e))
.collect();
let create_acps = create_acps.map_err(|e| {
ladmin_error!(audit, "Unable to parse create accesscontrols {:?}", e);
e
})?;
self.accesscontrols
.update_create(create_acps)
.map_err(|e| {
ladmin_error!(audit, "Failed to update create accesscontrols {:?}", e);
e
})?;
let filt = filter!(f_and!([
f_eq("class", PVCLASS_ACP.clone()),
f_eq("class", PVCLASS_ACM.clone()),
f_andnot(f_eq("acp_enable", PVACP_ENABLE_FALSE.clone())),
]));
let res = self.internal_search(audit, filt).map_err(|e| {
ladmin_error!(
audit,
"reload accesscontrols internal search failed {:?}",
e
);
e
})?;
let modify_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlModify::try_from(audit, self, e))
.collect();
let modify_acps = modify_acps.map_err(|e| {
ladmin_error!(audit, "Unable to parse modify accesscontrols {:?}", e);
e
})?;
self.accesscontrols
.update_modify(modify_acps)
.map_err(|e| {
ladmin_error!(audit, "Failed to update modify accesscontrols {:?}", e);
e
})?;
let filt = filter!(f_and!([
f_eq("class", PVCLASS_ACP.clone()),
f_eq("class", PVCLASS_ACD.clone()),
f_andnot(f_eq("acp_enable", PVACP_ENABLE_FALSE.clone())),
]));
let res = self.internal_search(audit, filt).map_err(|e| {
ladmin_error!(
audit,
"reload accesscontrols internal search failed {:?}",
e
);
e
})?;
let delete_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlDelete::try_from(audit, self, e))
.collect();
let delete_acps = delete_acps.map_err(|e| {
ladmin_error!(audit, "Unable to parse delete accesscontrols {:?}", e);
e
})?;
self.accesscontrols.update_delete(delete_acps).map_err(|e| {
ladmin_error!(audit, "Failed to update delete accesscontrols {:?}", e);
e
})
}
pub(crate) fn get_domain_uuid(&self) -> Uuid {
self.d_uuid
}
pub fn domain_rename(
&self,
audit: &mut AuditScope,
new_domain_name: &str,
) -> Result<(), OperationError> {
let modl =
ModifyList::new_purge_and_set("domain_name", Value::new_iname_s(new_domain_name));
let udi = PartialValue::new_uuidr(&UUID_DOMAIN_INFO);
let filt = filter_all!(f_eq("uuid", udi));
self.internal_modify(audit, filt, modl)
}
pub fn reindex(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
self.be_txn.reindex(audit)
}
pub(crate) fn upgrade_reindex(
&self,
audit: &mut AuditScope,
v: i64,
) -> Result<(), OperationError> {
self.be_txn.upgrade_reindex(audit, v)
}
pub fn commit(mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
if self.changed_schema.get() {
self.reload_schema(audit)?;
}
if self.changed_schema.get() || self.changed_acp.get() {
self.reload_accesscontrols(audit)?;
}
let QueryServerWriteTransaction {
committed,
be_txn,
schema,
accesscontrols,
cid,
..
} = self;
debug_assert!(!committed);
be_txn.set_db_ts_max(&cid.ts)?;
let r = schema.validate(audit);
if r.is_empty() {
schema
.commit()
.and_then(|_| accesscontrols.commit().and_then(|_| be_txn.commit(audit)))
} else {
Err(OperationError::ConsistencyError(r))
}
}
}
#[cfg(test)]
mod tests {
use crate::audit::AuditScope;
use crate::constants::{
CHANGELOG_MAX_AGE, JSON_ADMIN_V1, JSON_DOMAIN_INFO_V1, JSON_SYSTEM_CONFIG_V1,
JSON_SYSTEM_INFO_V1, RECYCLEBIN_MAX_AGE, SYSTEM_INDEX_VERSION, UUID_ADMIN,
UUID_DOMAIN_INFO,
};
use crate::credential::Credential;
use crate::entry::{Entry, EntryInit, EntryNew};
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, ReviveRecycledEvent, SearchEvent};
use crate::modify::{Modify, ModifyList};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::{OperationError, SchemaError};
use std::time::Duration;
use uuid::Uuid;
#[test]
fn test_qs_create_user() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let filt = filter!(f_eq("name", PartialValue::new_iname("testperson")));
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let se1 = unsafe { SearchEvent::new_impersonate_entry(admin.clone(), filt.clone()) };
let se2 = unsafe { SearchEvent::new_impersonate_entry(admin, filt) };
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e.clone()]);
let r1 = server_txn.search(audit, &se1).expect("search failure");
assert!(r1.is_empty());
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r2 = server_txn.search(audit, &se2).expect("search failure");
debug!("--> {:?}", r2);
assert!(r2.len() == 1);
let expected = unsafe { vec![e.into_sealed_committed()] };
assert_eq!(r2, expected);
assert!(server_txn.commit(audit).is_ok());
});
}
#[test]
fn test_qs_init_idempotent_schema_core() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
{
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn.initialise_schema_core(audit).is_ok());
}
{
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn.initialise_schema_core(audit).is_ok());
assert!(server_txn.initialise_schema_core(audit).is_ok());
assert!(server_txn.commit(audit).is_ok());
}
{
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn.initialise_schema_core(audit).is_ok());
}
{
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn.initialise_schema_core(audit).is_ok());
assert!(server_txn.commit(audit).is_ok());
}
});
}
#[test]
fn test_qs_modify() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson1"],
"displayname": ["testperson1"]
}
}"#,
);
let e2: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson2"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63932"],
"description": ["testperson2"],
"displayname": ["testperson2"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1.clone(), e2.clone()]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let me_emp = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_pres("class")),
ModifyList::new_list(vec![]),
)
};
assert!(server_txn.modify(audit, &me_emp) == Err(OperationError::EmptyRequest));
let me_nochg = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter!(f_eq("name", PartialValue::new_iname("flarbalgarble"))),
ModifyList::new_list(vec![Modify::Present(
"description".to_string(),
Value::from("anusaosu"),
)]),
)
};
assert!(server_txn.modify(audit, &me_nochg) == Err(OperationError::NoMatchingEntries));
let r_inv_1 = server_txn.internal_modify(
audit,
filter!(f_eq("tnanuanou", PartialValue::new_iname("Flarbalgarble"))),
ModifyList::new_list(vec![Modify::Present(
"description".to_string(),
Value::from("anusaosu"),
)]),
);
assert!(
r_inv_1
== Err(OperationError::SchemaViolation(
SchemaError::InvalidAttribute("tnanuanou".to_string())
))
);
let me_inv_m = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_pres("class")),
ModifyList::new_list(vec![Modify::Present(
"htnaonu".to_string(),
Value::from("anusaosu"),
)]),
)
};
assert!(
server_txn.modify(audit, &me_inv_m)
== Err(OperationError::SchemaViolation(
SchemaError::InvalidAttribute("htnaonu".to_string())
))
);
let me_sin = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("testperson2"))),
ModifyList::new_list(vec![Modify::Present(
"description".to_string(),
Value::from("anusaosu"),
)]),
)
};
assert!(server_txn.modify(audit, &me_sin).is_ok());
let me_mult = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_or!([
f_eq("name", PartialValue::new_iname("testperson1")),
f_eq("name", PartialValue::new_iname("testperson2")),
])),
ModifyList::new_list(vec![Modify::Present(
"description".to_string(),
Value::from("anusaosu"),
)]),
)
};
assert!(server_txn.modify(audit, &me_mult).is_ok());
assert!(server_txn.commit(audit).is_ok());
})
}
#[test]
fn test_modify_invalid_class() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson1"],
"displayname": ["testperson1"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1.clone()]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let me_sin = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("testperson1"))),
ModifyList::new_list(vec![Modify::Present(
"class".to_string(),
Value::new_class("system_info"),
)]),
)
};
assert!(server_txn.modify(audit, &me_sin).is_err());
let me_sin = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("testperson1"))),
ModifyList::new_list(vec![Modify::Present(
"name".to_string(),
Value::new_iname_s("testpersonx"),
)]),
)
};
assert!(server_txn.modify(audit, &me_sin).is_err());
let me_sin = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("testperson1"))),
ModifyList::new_list(vec![
Modify::Present("class".to_string(), Value::new_class("system_info")),
Modify::Present("version".to_string(), Value::new_uint32(1)),
]),
)
};
assert!(server_txn.modify(audit, &me_sin).is_ok());
let me_sin = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("testperson1"))),
ModifyList::new_list(vec![
Modify::Purged("name".to_string()),
Modify::Present("name".to_string(), Value::new_iname_s("testpersonx")),
]),
)
};
assert!(server_txn.modify(audit, &me_sin).is_ok());
})
}
#[test]
fn test_qs_delete() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let e2: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson2"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63932"],
"description": ["testperson"],
"displayname": ["testperson2"]
}
}"#,
);
let e3: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson3"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63933"],
"description": ["testperson"],
"displayname": ["testperson3"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1.clone(), e2.clone(), e3.clone()]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let de_inv =
unsafe { DeleteEvent::new_internal_invalid(filter!(f_pres("nhtoaunaoehtnu"))) };
assert!(server_txn.delete(audit, &de_inv).is_err());
let de_empty = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"uuid",
PartialValue::new_uuids("cc8e95b4-c24f-4d68-ba54-000000000000").unwrap()
)))
};
assert!(server_txn.delete(audit, &de_empty).is_err());
let de_sin = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"name",
PartialValue::new_iname("testperson3")
)))
};
assert!(server_txn.delete(audit, &de_sin).is_ok());
let de_mult = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"description",
PartialValue::new_utf8s("testperson")
)))
};
assert!(server_txn.delete(audit, &de_mult).is_ok());
assert!(server_txn.commit(audit).is_ok());
})
}
#[test]
fn test_qs_tombstone() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let time_p1 = duration_from_epoch_now();
let time_p2 = time_p1 + Duration::from_secs(CHANGELOG_MAX_AGE * 2);
let server_txn = server.write(time_p1);
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let filt_i_ts = filter_all!(f_eq("class", PartialValue::new_class("tombstone")));
let me_ts = unsafe {
ModifyEvent::new_impersonate_entry(
admin.clone(),
filt_i_ts.clone(),
ModifyList::new_list(vec![Modify::Present(
"class".to_string(),
Value::new_class("tombstone"),
)]),
)
};
let de_ts =
unsafe { DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_ts.clone()) };
let se_ts = unsafe { SearchEvent::new_ext_impersonate_entry(admin, filt_i_ts.clone()) };
let e_ts: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["tombstone", "object"],
"uuid": ["9557f49c-97a5-4277-a9a5-097d17eb8317"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e_ts]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r1 = server_txn.search(audit, &se_ts).expect("search failed");
assert!(r1.is_empty());
assert!(server_txn.delete(audit, &de_ts).is_err());
assert!(server_txn.modify(audit, &me_ts).is_err());
let r2 = server_txn
.internal_search(audit, filt_i_ts.clone())
.expect("internal search failed");
assert!(r2.len() == 1);
assert!(server_txn.purge_tombstones(audit).is_ok());
let r3 = server_txn
.internal_search(audit, filt_i_ts.clone())
.expect("internal search failed");
assert!(r3.len() == 1);
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(time_p2);
assert!(server_txn.purge_tombstones(audit).is_ok());
let r4 = server_txn
.internal_search(audit, filt_i_ts)
.expect("internal search failed");
assert!(r4.is_empty());
assert!(server_txn.commit(audit).is_ok());
})
}
#[test]
fn test_qs_recycle_simple() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let time_p1 = duration_from_epoch_now();
let time_p2 = time_p1 + Duration::from_secs(RECYCLEBIN_MAX_AGE * 2);
let server_txn = server.write(time_p1);
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let filt_i_rc = filter_all!(f_eq("class", PartialValue::new_class("recycled")));
let filt_i_ts = filter_all!(f_eq("class", PartialValue::new_class("tombstone")));
let filt_i_per = filter_all!(f_eq("class", PartialValue::new_class("person")));
let me_rc = unsafe {
ModifyEvent::new_impersonate_entry(
admin.clone(),
filt_i_rc.clone(),
ModifyList::new_list(vec![Modify::Present(
"class".to_string(),
Value::new_class("recycled"),
)]),
)
};
let de_rc =
unsafe { DeleteEvent::new_impersonate_entry(admin.clone(), filt_i_rc.clone()) };
let se_rc =
unsafe { SearchEvent::new_ext_impersonate_entry(admin.clone(), filt_i_rc.clone()) };
let sre_rc =
unsafe { SearchEvent::new_rec_impersonate_entry(admin.clone(), filt_i_rc.clone()) };
let rre_rc = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin,
filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
)
};
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "recycled"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let e2: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "recycled"],
"name": ["testperson2"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63932"],
"description": ["testperson"],
"displayname": ["testperson2"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1, e2]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r1 = server_txn.search(audit, &se_rc).expect("search failed");
assert!(r1.is_empty());
assert!(server_txn.delete(audit, &de_rc).is_err());
assert!(server_txn.modify(audit, &me_rc).is_err());
let r2 = server_txn.search(audit, &sre_rc).expect("search failed");
assert!(r2.len() == 2);
let r2 = server_txn
.internal_search(audit, filt_i_rc.clone())
.expect("internal search failed");
assert!(r2.len() == 2);
assert!(server_txn.revive_recycled(audit, &rre_rc).is_ok());
assert!(server_txn.purge_recycled(audit).is_ok());
let r3 = server_txn
.internal_search(audit, filt_i_rc.clone())
.expect("internal search failed");
assert!(r3.len() == 1);
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(time_p2);
assert!(server_txn.purge_recycled(audit).is_ok());
let r4 = server_txn
.internal_search(audit, filt_i_rc.clone())
.expect("internal search failed");
assert!(r4.is_empty());
let r5 = server_txn
.internal_search(audit, filt_i_ts.clone())
.expect("internal search failed");
assert!(r5.len() == 1);
let r6 = server_txn
.internal_search(audit, filt_i_per.clone())
.expect("internal search failed");
assert!(r6.len() == 1);
assert!(server_txn.commit(audit).is_ok());
})
}
#[test]
fn test_qs_recycle_advanced() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let de_sin = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"name",
PartialValue::new_iname("testperson1")
)))
};
assert!(server_txn.delete(audit, &de_sin).is_ok());
let filt_rc = filter_all!(f_eq("class", PartialValue::new_class("recycled")));
let sre_rc = unsafe { SearchEvent::new_rec_impersonate_entry(admin, filt_rc.clone()) };
let r2 = server_txn.search(audit, &sre_rc).expect("search failed");
assert!(r2.len() == 1);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_err());
assert!(server_txn.commit(audit).is_ok());
})
}
#[test]
fn test_qs_name_to_uuid() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r1 = server_txn.name_to_uuid(audit, "testpers");
assert!(r1.is_err());
let r2 = server_txn.name_to_uuid(audit, "tEsTpErS");
assert!(r2.is_err());
let r3 = server_txn.name_to_uuid(audit, "testperson1");
assert!(r3.is_ok());
let r4 = server_txn.name_to_uuid(audit, "tEsTpErSoN1");
assert!(r4.is_ok());
})
}
#[test]
fn test_qs_uuid_to_spn() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "account"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r1 = server_txn.uuid_to_spn(
audit,
&Uuid::parse_str("bae3f507-e6c3-44ba-ad01-f8ff1083534a").unwrap(),
);
assert!(r1 == Ok(None));
let r3 = server_txn.uuid_to_spn(
audit,
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
);
println!("{:?}", r3);
assert!(r3.unwrap().unwrap() == Value::new_spn_str("testperson1", "example.com"));
let r4 = server_txn.uuid_to_spn(
audit,
&Uuid::parse_str("CC8E95B4-C24F-4D68-BA54-8BED76F63930").unwrap(),
);
assert!(r4.unwrap().unwrap() == Value::new_spn_str("testperson1", "example.com"));
})
}
#[test]
fn test_qs_uuid_to_rdn() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "account"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r1 = server_txn.uuid_to_rdn(
audit,
&Uuid::parse_str("bae3f507-e6c3-44ba-ad01-f8ff1083534a").unwrap(),
);
assert!(r1.unwrap() == "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
let r3 = server_txn.uuid_to_rdn(
audit,
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
);
println!("{:?}", r3);
assert!(r3.unwrap() == "spn=testperson1@example.com");
let r4 = server_txn.uuid_to_rdn(
audit,
&Uuid::parse_str("CC8E95B4-C24F-4D68-BA54-8BED76F63930").unwrap(),
);
assert!(r4.unwrap() == "spn=testperson1@example.com");
})
}
#[test]
fn test_qs_uuid_to_star_recycle() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "account"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let tuuid = Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap();
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
assert!(
server_txn.uuid_to_rdn(audit, &tuuid)
== Ok("spn=testperson1@example.com".to_string())
);
assert!(
server_txn.uuid_to_spn(audit, &tuuid)
== Ok(Some(Value::new_spn_str("testperson1", "example.com")))
);
assert!(server_txn.name_to_uuid(audit, "testperson1") == Ok(tuuid));
let de_sin = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"name",
PartialValue::new_iname("testperson1")
)))
};
assert!(server_txn.delete(audit, &de_sin).is_ok());
assert!(
server_txn.uuid_to_rdn(audit, &tuuid)
== Ok("uuid=cc8e95b4-c24f-4d68-ba54-8bed76f63930".to_string())
);
assert!(server_txn.uuid_to_spn(audit, &tuuid) == Ok(None));
assert!(server_txn.name_to_uuid(audit, "testperson1").is_err());
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let rre_rc = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin,
filter_all!(f_eq("name", PartialValue::new_iname("testperson1"))),
)
};
assert!(server_txn.revive_recycled(audit, &rre_rc).is_ok());
assert!(
server_txn.uuid_to_rdn(audit, &tuuid)
== Ok("spn=testperson1@example.com".to_string())
);
assert!(
server_txn.uuid_to_spn(audit, &tuuid)
== Ok(Some(Value::new_spn_str("testperson1", "example.com")))
);
assert!(server_txn.name_to_uuid(audit, "testperson1") == Ok(tuuid));
})
}
#[test]
fn test_qs_clone_value() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let r1 =
server_txn.clone_value(audit, &"tausau".to_string(), &"naoeutnhaou".to_string());
assert!(r1.is_err());
let r2 = server_txn.clone_value(audit, &"NaMe".to_string(), &"NaMe".to_string());
assert!(r2.is_err());
let r3 =
server_txn.clone_value(audit, &"member".to_string(), &"testperson1".to_string());
assert!(r3 == Ok(Value::new_refer_s("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap()));
let r4 = server_txn.clone_value(
audit,
&"member".to_string(),
&"cc8e95b4-c24f-4d68-ba54-8bed76f63930".to_string(),
);
debug!("{:?}", r4);
assert!(r4 == Ok(Value::new_refer_s("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap()));
})
}
#[test]
fn test_qs_resolve_value() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "account"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let e2: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"name": ["testperson2"],
"uuid": ["a67c0c71-0b35-4218-a6b0-22d23d131d27"],
"description": ["testperson"],
"displayname": ["testperson2"]
}
}"#,
);
let e_ts: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["tombstone", "object"],
"uuid": ["9557f49c-97a5-4277-a9a5-097d17eb8317"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e1, e2, e_ts]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let t1 = Value::new_utf8s("teststring");
let r1 = server_txn.resolve_value(audit, &t1);
assert!(r1 == Ok("teststring".to_string()));
let t_uuid = Value::new_refer_s("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap();
let r_uuid = server_txn.resolve_value(audit, &t_uuid);
debug!("{:?}", r_uuid);
assert!(r_uuid == Ok("testperson1@example.com".to_string()));
let t_uuid = Value::new_refer_s("a67c0c71-0b35-4218-a6b0-22d23d131d27").unwrap();
let r_uuid = server_txn.resolve_value(audit, &t_uuid);
debug!("{:?}", r_uuid);
assert!(r_uuid == Ok("testperson2".to_string()));
let t_uuid_non = Value::new_refer_s("b83e98f0-3d2e-41d2-9796-d8d993289c86").unwrap();
let r_uuid_non = server_txn.resolve_value(audit, &t_uuid_non);
debug!("{:?}", r_uuid_non);
assert!(r_uuid_non == Ok("b83e98f0-3d2e-41d2-9796-d8d993289c86".to_string()));
let t_uuid_ts = Value::new_refer_s("9557f49c-97a5-4277-a9a5-097d17eb8317").unwrap();
let r_uuid_ts = server_txn.resolve_value(audit, &t_uuid_ts);
debug!("{:?}", r_uuid_ts);
assert!(r_uuid_ts == Ok("9557f49c-97a5-4277-a9a5-097d17eb8317".to_string()));
})
}
#[test]
fn test_qs_dynamic_schema_class() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "testclass"],
"name": ["testobj1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
}
}"#,
);
let e_cd: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "classtype"],
"classname": ["testclass"],
"uuid": ["cfcae205-31c3-484b-8ced-667d1709c5e3"],
"description": ["Test Class"],
"may": ["name"]
}
}"#,
);
let server_txn = server.write(duration_from_epoch_now());
let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
assert!(server_txn.create(audit, &ce_class).is_ok());
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_fail).is_err());
server_txn.commit(audit).expect("should not fail");
let server_txn = server.write(duration_from_epoch_now());
let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_work).is_ok());
server_txn.commit(audit).expect("should not fail");
let server_txn = server.write(duration_from_epoch_now());
let de_class = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"classname",
PartialValue::new_class("testclass")
)))
};
assert!(server_txn.delete(audit, &de_class).is_ok());
server_txn.commit(audit).expect("should not fail");
let server_txn = server.write(duration_from_epoch_now());
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_fail).is_err());
let testobj1 = server_txn
.internal_search_uuid(
audit,
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
)
.expect("failed");
assert!(testobj1.attribute_value_pres("class", &PartialValue::new_class("testclass")));
server_txn.commit(audit).expect("should not fail");
})
}
#[test]
fn test_qs_dynamic_schema_attr() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "extensibleobject"],
"name": ["testobj1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"testattr": ["test"]
}
}"#,
);
let e_ad: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "attributetype"],
"attributename": ["testattr"],
"uuid": ["cfcae205-31c3-484b-8ced-667d1709c5e3"],
"description": ["Test Attribute"],
"multivalue": ["false"],
"unique": ["false"],
"syntax": ["UTF8STRING"]
}
}"#,
);
let server_txn = server.write(duration_from_epoch_now());
let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
assert!(server_txn.create(audit, &ce_attr).is_ok());
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_fail).is_err());
server_txn.commit(audit).expect("should not fail");
let server_txn = server.write(duration_from_epoch_now());
let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_work).is_ok());
server_txn.commit(audit).expect("should not fail");
let server_txn = server.write(duration_from_epoch_now());
let de_attr = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_eq(
"attributename",
PartialValue::new_iutf8s("testattr")
)))
};
assert!(server_txn.delete(audit, &de_attr).is_ok());
server_txn.commit(audit).expect("should not fail");
let server_txn = server.write(duration_from_epoch_now());
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_fail).is_err());
let filt = filter!(f_eq("testattr", PartialValue::new_utf8s("test")));
assert!(server_txn.internal_search(audit, filt).is_err());
let testobj1 = server_txn
.internal_search_uuid(
audit,
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
)
.expect("failed");
assert!(testobj1.attribute_value_pres("testattr", &PartialValue::new_utf8s("test")));
server_txn.commit(audit).expect("should not fail");
})
}
#[test]
fn test_qs_modify_password_only() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person", "account"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
);
let server_txn = server.write(duration_from_epoch_now());
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let cred = Credential::new_password_only("test_password");
let v_cred = Value::new_credential("primary", cred);
assert!(v_cred.validate());
let me_inv_m = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("testperson1"))),
ModifyList::new_list(vec![Modify::Present(
"primary_credential".to_string(),
v_cred,
)]),
)
};
assert!(server_txn.modify(audit, &me_inv_m).is_ok());
let test_ent = server_txn
.internal_search_uuid(
audit,
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
)
.expect("failed");
let cred_ref = test_ent
.get_ava_single_credential("primary_credential")
.expect("Failed");
assert!(cred_ref.verify_password("test_password"));
})
}
fn create_user(name: &str, uuid: &str) -> Entry<EntryInit, EntryNew> {
let mut e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "person"],
"description": ["testperson-entry"]
}
}"#,
);
e1.add_ava("uuid", Value::new_uuids(uuid).unwrap());
e1.add_ava("name", Value::new_iname_s(name));
e1.add_ava("displayname", Value::new_utf8s(name));
e1
}
fn create_group(name: &str, uuid: &str, members: &[&str]) -> Entry<EntryInit, EntryNew> {
let mut e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "group"],
"description": ["testgroup-entry"]
}
}"#,
);
e1.add_ava("name", Value::new_iname_s(name));
e1.add_ava("uuid", Value::new_uuids(uuid).unwrap());
members
.iter()
.for_each(|m| e1.add_ava("member", Value::new_refer_s(m).unwrap()));
e1
}
fn check_entry_has_mo(
qs: &QueryServerWriteTransaction,
audit: &mut AuditScope,
name: &str,
mo: &str,
) -> bool {
let e = qs
.internal_search(audit, filter!(f_eq("name", PartialValue::new_iname(name))))
.unwrap()
.pop()
.unwrap();
e.attribute_value_pres("memberof", &PartialValue::new_refer_s(mo).unwrap())
}
#[test]
fn test_qs_revive_advanced_directmemberships() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let u1 = create_user("u1", "22b47373-d123-421f-859e-9ddd8ab14a2a");
let g1 = create_group(
"g1",
"cca2bbfc-5b43-43f3-be9e-f5b03b3defec",
&["22b47373-d123-421f-859e-9ddd8ab14a2a"],
);
let u2 = create_user("u2", "5c19a4a2-b9f0-4429-b130-5782de5fddda");
let g2a = create_group(
"g2a",
"e44cf9cd-9941-44cb-a02f-307b6e15ac54",
&["5c19a4a2-b9f0-4429-b130-5782de5fddda"],
);
let g2b = create_group(
"g2b",
"d3132e6e-18ce-4b87-bee1-1d25e4bfe96d",
&["e44cf9cd-9941-44cb-a02f-307b6e15ac54"],
);
let u3 = create_user("u3", "68467a41-6e8e-44d0-9214-a5164e75ca03");
let g3 = create_group(
"g3",
"36048117-e479-45ed-aeb5-611e8d83d5b1",
&["68467a41-6e8e-44d0-9214-a5164e75ca03"],
);
let u4 = create_user("u4", "d696b10f-1729-4f1a-83d0-ca06525c2f59");
let g4 = create_group(
"g4",
"d5c59ac6-c533-4b00-989f-d0e183f07bab",
&["d696b10f-1729-4f1a-83d0-ca06525c2f59"],
);
let ce = CreateEvent::new_internal(vec![u1, g1, u2, g2a, g2b, u3, g3, u4, g4]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
let de = unsafe {
DeleteEvent::new_internal_invalid(filter!(f_or(vec![
f_eq("name", PartialValue::new_iname("u1")),
f_eq("name", PartialValue::new_iname("u2")),
f_eq("name", PartialValue::new_iname("u3")),
f_eq("name", PartialValue::new_iname("g3")),
f_eq("name", PartialValue::new_iname("u4")),
f_eq("name", PartialValue::new_iname("g4"))
])))
};
assert!(server_txn.delete(audit, &de).is_ok());
let rev1 = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin.clone(),
filter_all!(f_eq("name", PartialValue::new_iname("u1"))),
)
};
assert!(server_txn.revive_recycled(audit, &rev1).is_ok());
assert!(check_entry_has_mo(
&server_txn,
audit,
"u1",
"cca2bbfc-5b43-43f3-be9e-f5b03b3defec"
));
let rev2 = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin.clone(),
filter_all!(f_eq("name", PartialValue::new_iname("u2"))),
)
};
assert!(server_txn.revive_recycled(audit, &rev2).is_ok());
assert!(check_entry_has_mo(
&server_txn,
audit,
"u2",
"e44cf9cd-9941-44cb-a02f-307b6e15ac54"
));
assert!(check_entry_has_mo(
&server_txn,
audit,
"u2",
"d3132e6e-18ce-4b87-bee1-1d25e4bfe96d"
));
let rev3 = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin.clone(),
filter_all!(f_or(vec![
f_eq("name", PartialValue::new_iname("u3")),
f_eq("name", PartialValue::new_iname("g3"))
])),
)
};
assert!(server_txn.revive_recycled(audit, &rev3).is_ok());
assert!(
check_entry_has_mo(
&server_txn,
audit,
"u3",
"36048117-e479-45ed-aeb5-611e8d83d5b1"
) == false
);
let rev4a = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin.clone(),
filter_all!(f_eq("name", PartialValue::new_iname("u4"))),
)
};
assert!(server_txn.revive_recycled(audit, &rev4a).is_ok());
assert!(
check_entry_has_mo(
&server_txn,
audit,
"u4",
"d5c59ac6-c533-4b00-989f-d0e183f07bab"
) == false
);
let rev4b = unsafe {
ReviveRecycledEvent::new_impersonate_entry(
admin,
filter_all!(f_eq("name", PartialValue::new_iname("g4"))),
)
};
assert!(server_txn.revive_recycled(audit, &rev4b).is_ok());
assert!(
check_entry_has_mo(
&server_txn,
audit,
"u4",
"d5c59ac6-c533-4b00-989f-d0e183f07bab"
) == false
);
assert!(server_txn.commit(audit).is_ok());
})
}
#[test]
fn test_qs_upgrade_entry_attrs() {
run_test_no_init!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn
.upgrade_reindex(audit, SYSTEM_INDEX_VERSION)
.is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
server_txn.initialise_schema_core(audit).unwrap();
server_txn.initialise_schema_idm(audit).unwrap();
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn
.upgrade_reindex(audit, SYSTEM_INDEX_VERSION + 1)
.is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn
.internal_migrate_or_create_str(audit, JSON_SYSTEM_INFO_V1)
.is_ok());
assert!(server_txn
.internal_migrate_or_create_str(audit, JSON_DOMAIN_INFO_V1)
.is_ok());
assert!(server_txn
.internal_migrate_or_create_str(audit, JSON_SYSTEM_CONFIG_V1)
.is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
let me_syn = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_or!([
f_eq("attributename", PartialValue::new_iutf8s("name")),
f_eq("attributename", PartialValue::new_iutf8s("domain_name")),
])),
ModifyList::new_purge_and_set(
"syntax",
Value::new_syntaxs("UTF8STRING_INSENSITIVE").unwrap(),
),
)
};
assert!(server_txn.modify(audit, &me_syn).is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
let me_dn = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("uuid", PartialValue::new_uuidr(&UUID_DOMAIN_INFO))),
ModifyList::new_list(vec![
Modify::Purged("name".to_string()),
Modify::Purged("domain_name".to_string()),
Modify::Present("name".to_string(), Value::new_iutf8s("domain_local")),
Modify::Present(
"domain_name".to_string(),
Value::new_iutf8s("example.com"),
),
]),
)
};
assert!(server_txn.modify(audit, &me_dn).is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
let me_syn = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_or!([
f_eq("attributename", PartialValue::new_iutf8s("name")),
f_eq("attributename", PartialValue::new_iutf8s("domain_name")),
])),
ModifyList::new_purge_and_set(
"syntax",
Value::new_syntaxs("UTF8STRING_INAME").unwrap(),
),
)
};
assert!(server_txn.modify(audit, &me_syn).is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
assert!(server_txn.migrate_2_to_3(audit).is_ok());
assert!(server_txn.commit(audit).is_ok());
let server_txn = server.write(duration_from_epoch_now());
let domain = server_txn
.internal_search_uuid(audit, &UUID_DOMAIN_INFO)
.expect("failed");
domain
.get_ava("name")
.expect("no name?")
.for_each(|v| assert!(v.is_iname()));
domain
.get_ava("domain_name")
.expect("no domain_name?")
.for_each(|v| assert!(v.is_iname()));
assert!(server_txn.commit(audit).is_ok());
})
}
}