diff --git a/Cargo.toml b/Cargo.toml index e879fc438..7616ebf56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ -cargo-features = ["default-run"] +# cargo-features = ["default-run"] [package] name = "rsidm" version = "0.1.0" authors = ["William Brown "] -default-run = "rsidm_core" +# default-run = "rsidm_core" edition = "2018" @@ -33,6 +33,7 @@ env_logger = "0.5" reqwest = "0.9" chrono = "0.4" +cookie = "0.11" regex = "1" lazy_static = "1.2.0" diff --git a/designs/memberof.rst b/designs/memberof.rst index 3cbb9292b..917be8fbe 100644 --- a/designs/memberof.rst +++ b/designs/memberof.rst @@ -153,7 +153,13 @@ changes occur, we have completed the operation. Considerations -------------- -* Preventing recursion: As of course, we are +* Preventing recursion: As of course, we are using a recursive algo, it has to end. The base case +is "is there no groups with differences" which causes us to NO-OP and return. -* Replication +* Replication; Because each server has MO, then content of the member of should be consistent. However +what should be considered is the changelog items to ensure that the member changes are accurately +reflected inside of the members. + +* Fixup: Simply apply a modify of "purged: *memberof*", and that should cause +recalculation. (testing needed). diff --git a/src/lib/be/mod.rs b/src/lib/be/mod.rs index bddced695..6b1cfab1e 100644 --- a/src/lib/be/mod.rs +++ b/src/lib/be/mod.rs @@ -474,7 +474,7 @@ impl BackendWriteTransaction { }) } - pub fn backup(&self, audit: &mut AuditScope, dstPath: &str) -> Result<(), OperationError> { + pub fn backup(&self, audit: &mut AuditScope, dst_path: &str) -> Result<(), OperationError> { // load all entries into RAM, may need to change this later // if the size of the database compared to RAM is an issue let mut raw_entries: Vec = Vec::new(); @@ -512,16 +512,16 @@ impl BackendWriteTransaction { let entries = entries?; - let mut serializedEntries = serde_json::to_string_pretty(&entries); + let serialized_entries = serde_json::to_string_pretty(&entries); - let serializedEntriesStr = try_audit!( + let serialized_entries_str = try_audit!( audit, - serializedEntries, + serialized_entries, "serde error {:?}", OperationError::SerdeJsonError ); - let result = fs::write(dstPath, serializedEntriesStr); + let result = fs::write(dst_path, serialized_entries_str); try_audit!( audit, @@ -545,28 +545,26 @@ impl BackendWriteTransaction { Ok(()) } - pub fn restore(&self, audit: &mut AuditScope, srcPath: &str) -> Result<(), OperationError> { + pub fn restore(&self, audit: &mut AuditScope, src_path: &str) -> Result<(), OperationError> { // load all entries into RAM, may need to change this later // if the size of the database compared to RAM is an issue - let mut serializedStringOption = fs::read_to_string(srcPath); + let serialized_string_option = fs::read_to_string(src_path); - let mut serializedString = try_audit!( + let serialized_string = try_audit!( audit, - serializedStringOption, + serialized_string_option, "fs::read_to_string {:?}", OperationError::FsError ); - unsafe { - self.purge(audit); - } + try_audit!(audit, unsafe { self.purge(audit) }); - let entriesOption: Result, serde_json::Error> = - serde_json::from_str(&serializedString); + let entries_option: Result, serde_json::Error> = + serde_json::from_str(&serialized_string); let entries = try_audit!( audit, - entriesOption, + entries_option, "serde_json error {:?}", OperationError::SerdeJsonError ); diff --git a/src/lib/constants.rs b/src/lib/constants.rs index 5d80b81c8..dcbf73fb5 100644 --- a/src/lib/constants.rs +++ b/src/lib/constants.rs @@ -61,6 +61,7 @@ pub static UUID_SCHEMA_ATTR_MEMBEROF: &'static str = "2ff1abc8-2f64-4f41-9e3d-33 pub static UUID_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = "52f2f13f-d35c-4cca-9f43-90a12c968f72"; pub static UUID_SCHEMA_ATTR_PASSWORD: &'static str = "a5121082-be54-4624-a307-383839b0366b"; pub static UUID_SCHEMA_ATTR_MEMBER: &'static str = "cbb7cb55-1d48-4b89-8da7-8d570e755b47"; +pub static UUID_SCHEMA_ATTR_DIRECTMEMBEROF: &'static str = "63f6a766-3838-48e3-bd78-0fb1152b862f"; pub static UUID_SCHEMA_ATTR_VERSION: &'static str = "896d5095-b3ae-451e-a91f-4314165b5395"; pub static UUID_SCHEMA_ATTR_DOMAIN: &'static str = "c9926716-eaaa-4c83-a1ab-1ed4372a7491"; diff --git a/src/lib/core.rs b/src/lib/core.rs index 28aabec08..79103b240 100644 --- a/src/lib/core.rs +++ b/src/lib/core.rs @@ -250,9 +250,9 @@ pub fn create_server_core(config: Configuration) { // be generated (probably stored in DB for cross-host access) session::CookieSessionBackend::signed(&[0; 32]) .path("/") - //.max_age() duration of the token life - // .domain() - //.same_site() constraunt to the domain + //.max_age() duration of the token life TODO make this proper! + .domain("localhost") + .same_site(cookie::SameSite::Strict) // constrain to the domain // Disallow from js .http_only(true) .name("rsidm-session") diff --git a/src/lib/entry.rs b/src/lib/entry.rs index 0eb3c13d0..51f3e9f22 100644 --- a/src/lib/entry.rs +++ b/src/lib/entry.rs @@ -143,6 +143,12 @@ pub struct Entry { attrs: BTreeMap>, } +impl std::fmt::Display for Entry { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.get_uuid()) + } +} + impl Entry { #[cfg(test)] pub fn new() -> Self { @@ -475,6 +481,7 @@ impl Entry { } } + /* pub fn seal(self) -> Entry { Entry { valid: self.valid, @@ -484,6 +491,16 @@ impl Entry { attrs: self.attrs, } } + */ + + pub fn get_uuid(&self) -> &String { + // TODO: Make this not unwrap!!! + self.attrs + .get("uuid") + .expect("UUID ATTR NOT PRESENT, INVALID ENTRY STATE!!!") + .first() + .expect("UUID VALUE NOT PRESENT, INVALID ENTRY STATE!!!") + } // Assert if this filter matches the entry (no index) pub fn entry_match_no_index(&self, filter: &Filter) -> bool { @@ -612,6 +629,7 @@ impl Entry { } impl Entry { + /* WARNING: Should these TODO move to EntryValid only? */ pub fn get_ava(&self, attr: &String) -> Option<&Vec> { self.attrs.get(attr) } @@ -621,6 +639,16 @@ impl Entry { self.attrs.contains_key(attr) } + pub fn attribute_value_pres(&self, attr: &str, value: &str) -> bool { + match self.attrs.get(attr) { + Some(v_list) => match v_list.binary_search(&value.to_string()) { + Ok(_) => true, + Err(_) => false, + }, + None => false, + } + } + pub fn attribute_equality(&self, attr: &str, value: &str) -> bool { // we assume based on schema normalisation on the way in // that the equality here of the raw values MUST be correct. @@ -725,31 +753,19 @@ where } // Should this be schemaless, relying on checks of the modlist, and the entry validate after? - pub fn apply_modlist( - &self, - modlist: &ModifyList, - ) -> Result, OperationError> { + pub fn apply_modlist(&mut self, modlist: &ModifyList) { + // -> Result, OperationError> { // Apply a modlist, generating a new entry that conforms to the changes. // This is effectively clone-and-transform - // clone the entry - let mut ne: Entry = Entry { - valid: self.valid, - state: self.state, - attrs: self.attrs.clone(), - }; - // mutate for modify in modlist { match modify { - Modify::Present(a, v) => ne.add_ava(a.clone(), v.clone()), - Modify::Removed(a, v) => ne.remove_ava(a, v), - Modify::Purged(a) => ne.purge_ava(a), + Modify::Present(a, v) => self.add_ava(a.clone(), v.clone()), + Modify::Removed(a, v) => self.remove_ava(a, v), + Modify::Purged(a) => self.purge_ava(a), } } - - // return it - Ok(ne) } } @@ -798,7 +814,7 @@ struct User { mod tests { use crate::entry::{Entry, EntryInvalid, EntryNew}; use crate::modify::{Modify, ModifyList}; - use serde_json; + // use serde_json; #[test] fn test_entry_basic() { @@ -859,10 +875,10 @@ mod tests { )]) }; - let ne = e.apply_modlist(&mods).expect("Failed to apply modlist"); + e.apply_modlist(&mods); // Assert the changes are there - assert!(ne.attribute_equality("attr", "value")); + assert!(e.attribute_equality("attr", "value")); // Assert present for multivalue // Assert purge on single/multi/empty value diff --git a/src/lib/error.rs b/src/lib/error.rs index 9222775e4..fd0886fed 100644 --- a/src/lib/error.rs +++ b/src/lib/error.rs @@ -43,4 +43,5 @@ pub enum ConsistencyError { UuidIndexCorrupt(String), UuidNotUnique(String), RefintNotUpheld(u64), + MemberOfInvalid(u64), } diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 248b9dd9f..0c203c3e8 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -12,6 +12,7 @@ extern crate uuid; extern crate bytes; extern crate chrono; +extern crate cookie; extern crate env_logger; extern crate regex; diff --git a/src/lib/plugins/base.rs b/src/lib/plugins/base.rs index 02e7188a2..2ed5f073e 100644 --- a/src/lib/plugins/base.rs +++ b/src/lib/plugins/base.rs @@ -243,12 +243,12 @@ impl Plugin for Base { #[cfg(test)] mod tests { - #[macro_use] - use crate::plugins::Plugin; + // #[macro_use] + // use crate::plugins::Plugin; use crate::entry::{Entry, EntryInvalid, EntryNew}; use crate::error::OperationError; use crate::filter::Filter; - use crate::modify::{Modify, ModifyInvalid, ModifyList}; + use crate::modify::{Modify, ModifyList}; use crate::server::QueryServerReadTransaction; use crate::server::QueryServerWriteTransaction; diff --git a/src/lib/plugins/macros.rs b/src/lib/plugins/macros.rs index 5e1558607..ba4d95072 100644 --- a/src/lib/plugins/macros.rs +++ b/src/lib/plugins/macros.rs @@ -60,9 +60,14 @@ macro_rules! run_create_test { let r = qs_write.create(&mut au_test, &ce); assert!(r == $expect); $check(&mut au_test, &qs_write); - r.map(|_| { - assert!(qs_write.commit(&mut au_test).is_ok()); - }); + match r { + Ok(_) => { + qs_write.commit(&mut au_test).expect("commit failure!"); + } + Err(e) => { + audit_log!(&mut au_test, "Rolling back => {:?}", e); + } + } } // Make sure there are no errors. assert!(qs.verify(&mut au_test).len() == 0); @@ -107,9 +112,14 @@ macro_rules! run_modify_test { let r = qs_write.modify(&mut au_test, &me); $check(&mut au_test, &qs_write); assert!(r == $expect); - r.map(|_| { - assert!(qs_write.commit(&mut au_test).is_ok()); - }); + match r { + Ok(_) => { + qs_write.commit(&mut au_test).expect("commit failure!"); + } + Err(e) => { + audit_log!(&mut au_test, "Rolling back => {:?}", e); + } + } } // Make sure there are no errors. assert!(qs.verify(&mut au_test).len() == 0); @@ -153,9 +163,14 @@ macro_rules! run_delete_test { let r = qs_write.delete(&mut au_test, &de); $check(&mut au_test, &qs_write); assert!(r == $expect); - r.map(|_| { - assert!(qs_write.commit(&mut au_test).is_ok()); - }); + match r { + Ok(_) => { + qs_write.commit(&mut au_test).expect("commit failure!"); + } + Err(e) => { + audit_log!(&mut au_test, "Rolling back => {:?}", e); + } + } } // Make sure there are no errors. assert!(qs.verify(&mut au_test).len() == 0); diff --git a/src/lib/plugins/memberof.rs b/src/lib/plugins/memberof.rs new file mode 100644 index 000000000..e9d6282ca --- /dev/null +++ b/src/lib/plugins/memberof.rs @@ -0,0 +1,1593 @@ +// Member Of +// +// Generate reverse relationships for groups to their members. +// +// Note referential integrity MUST be run first - this is to avoid the situation +// demonstrated in test_delete_mo_multi_cycle - that is, when we delete B, we trigger +// an update to C. C then triggers to A, which re-reades C + D. Because D still has not +// been update, it's stale reference to B flows to A, causing refint to fail the mod. +// +// As a result, we first need to run refint to clean up all dangling references, then memberof +// fixes the graph of memberships + +use crate::audit::AuditScope; +use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; +use crate::error::{ConsistencyError, OperationError}; +use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; +use crate::filter::{Filter, FilterInvalid}; +use crate::modify::{Modify, ModifyList, ModifyValid}; +use crate::plugins::Plugin; +use crate::server::QueryServerReadTransaction; +use crate::server::{QueryServerTransaction, QueryServerWriteTransaction}; + +use std::collections::BTreeMap; + +pub struct MemberOf; + +fn affected_uuids<'a, STATE>( + au: &mut AuditScope, + changed: Vec<&'a Entry>, +) -> Vec<&'a String> +where + STATE: std::fmt::Debug, +{ + // From the list of groups which were changed in this operation: + let changed_groups: Vec<_> = changed + .into_iter() + .filter(|e| e.attribute_value_pres("class", "group")) + .inspect(|e| { + audit_log!(au, "group reporting change: {:?}", e); + }) + .collect(); + + // Now, build a map of all UUID's that will require updates as a result of this change + let mut affected_uuids: Vec<&String> = changed_groups + .iter() + .filter_map(|e| { + // Only groups with member get collected up here. + e.get_ava(&"member".to_string()) + }) + // Flatten the member's to the list. + .flatten() + .collect(); + + // Sort + // TODO: promote groups to head of the affected_uuids set! + // this could be assisted by indexing in the future by providing a custom compare + // algo!!! + affected_uuids.sort(); + // Remove dups + affected_uuids.dedup(); + + affected_uuids +} + +fn apply_memberof( + au: &mut AuditScope, + qs: &QueryServerWriteTransaction, + affected_uuids: Vec<&String>, +) -> Result<(), OperationError> { + audit_log!(au, " => entering apply_memberof"); + audit_log!(au, "affected uuids -> {:?}", affected_uuids); + + // Apply member takes a list of changes. We then filter that to only the changed groups + // and using this, we determine a list of UUID's from members that will be required to + // re-examine their MO attributes. + + // Given the list of UUID that require changes, we attempt to trigger MO updates on groups + // first to stabilise the MO graph before we start triggering changes on entries. + // + // it's important to note that each change itself, especially groups, could trigger there + // own recursive updates until the base case - stable, no changes - is reached. + // + // That means the termination of recursion is ALWAYS to be found in the post_modify + // callback, as regardless of initial entry point, all subsequent MO internal operations + // are modifies - it is up to post_modify to break cycles! + + // Now work on the affected set. + + // For each affected uuid + for a_uuid in affected_uuids { + // search where group + Eq("member": "uuid") + let groups = try_audit!( + au, + qs.internal_search( + au, + Filter::new_ignore_hidden(Filter::And(vec![ + Filter::Eq("class".to_string(), "group".to_string()), + Filter::Eq("member".to_string(), a_uuid.to_string()), + ])) + ) + ); + // get UUID of all groups + all memberof values + let mut dir_mo_set: Vec<_> = groups.iter().map(|g| g.get_uuid().clone()).collect(); + + // No need to dedup this. Sorting could be of questionable + // value too though ... + dir_mo_set.sort(); + + let mut mo_set: Vec<_> = groups + .iter() + .map(|g| { + // TODO: This could be more effecient + let mut v = vec![g.get_uuid().clone()]; + match g.get_ava(&"memberof".to_string()) { + Some(mos) => { + for mo in mos { + v.push(mo.clone()) + } + } + None => {} + } + v + }) + .flatten() + .collect(); + + mo_set.sort(); + mo_set.dedup(); + + audit_log!(au, "Updating {:?} to be dir mo {:?}", a_uuid, dir_mo_set); + audit_log!(au, "Updating {:?} to be mo {:?}", a_uuid, mo_set); + + // first add a purged memberof to remove all mo we no longer + // support. + // TODO: Could this be more efficient + let mo_purge = vec![ + Modify::Purged("memberof".to_string()), + Modify::Purged("directmemberof".to_string()), + ]; + + // create modify present memberof all uuids + let mod_set: Vec<_> = mo_purge + .into_iter() + .chain( + mo_set + .into_iter() + .map(|mo_uuid| Modify::Present("memberof".to_string(), mo_uuid)), + ) + .chain( + dir_mo_set + .into_iter() + .map(|mo_uuid| Modify::Present("directmemberof".to_string(), mo_uuid)), + ) + .collect(); + + // apply to affected uuid + let modlist = ModifyList::new_list(mod_set); + + try_audit!( + au, + qs.internal_modify( + au, + Filter::Eq("uuid".to_string(), a_uuid.to_string()), + modlist, + ) + ); + } + + Ok(()) +} + +impl Plugin for MemberOf { + fn id() -> &'static str { + "memberof" + } + + // TODO: We could make this more effecient by limiting change detection to ONLY member/memberof + // attrs rather than any attrs. + + fn post_create( + au: &mut AuditScope, + qs: &QueryServerWriteTransaction, + cand: &Vec>, + _ce: &CreateEvent, + ) -> Result<(), OperationError> { + // + // Trigger apply_memberof on all because they changed. + let cand_refs: Vec<&Entry<_, _>> = cand.iter().map(|e| e).collect(); + let uuids = affected_uuids(au, cand_refs); + apply_memberof(au, qs, uuids) + } + + fn post_modify( + au: &mut AuditScope, + qs: &QueryServerWriteTransaction, + pre_cand: &Vec>, + cand: &Vec>, + _me: &ModifyEvent, + _modlist: &ModifyList, + ) -> Result<(), OperationError> { + // The condition here is critical - ONLY trigger on entries where changes occur! + let mut changed: Vec<&String> = pre_cand + .iter() + .zip(cand.iter()) + .filter(|(pre, post)| { + // This is the base case to break cycles in recursion! + pre != post + && ( + // AND if it was a group, or will become a group. + post.attribute_value_pres("class", "group") + || pre.attribute_value_pres("class", "group") + ) + }) + // Flatten the pre-post tuples. We no longer care if it was + // pre-post + // TODO: Could this be more effecient? + .flat_map(|(pre, post)| vec![pre, post]) + .inspect(|e| { + audit_log!(au, "group reporting change: {:?}", e); + }) + .filter_map(|e| { + // Only groups with member get collected up here. + e.get_ava(&"member".to_string()) + }) + // Flatten the uuid lists. + .flatten() + .collect(); + + // Now tidy them up. + changed.sort(); + changed.dedup(); + + apply_memberof(au, qs, changed) + } + + fn pre_delete( + au: &mut AuditScope, + _qs: &QueryServerWriteTransaction, + cand: &mut Vec>, + _de: &DeleteEvent, + ) -> Result<(), OperationError> { + // It is not valid for a recycled group to be considered + // a member of any other type. We simply purge the ava from + // the entries. This is because it will be removed from all + // locations where it *was* a member. + // + // As a result, on restore, the graph of where it was a member + // would have to be rebuilt. + // + // AN interesting possibility could be NOT to purge MO on delete + // and use that to rebuild the forward graph of member -> item, but + // due to the nature of MO, we do not know the difference between + // direct and indirect membership, meaning we would be safer + // to not do this. + + // NOTE: DO NOT purge directmemberof - we use that to restore memberships + // in recycle revive! + + cand.iter_mut() + .for_each(|e| e.purge_ava(&"memberof".to_string())); + Ok(()) + } + + fn post_delete( + au: &mut AuditScope, + qs: &QueryServerWriteTransaction, + cand: &Vec>, + _ce: &DeleteEvent, + ) -> Result<(), OperationError> { + // + // Trigger apply_memberof on all - because they all changed. + let cand_refs: Vec<&Entry<_, _>> = cand.iter().map(|e| e).collect(); + let uuids = affected_uuids(au, cand_refs); + apply_memberof(au, qs, uuids) + } + + fn verify( + au: &mut AuditScope, + qs: &QueryServerTransaction, + ) -> Vec> { + let mut r = Vec::new(); + + let filt_in: Filter = + Filter::new_ignore_hidden(Filter::Pres("class".to_string())); + + let all_cand = match qs + .internal_search(au, filt_in) + .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure)) + { + Ok(all_cand) => all_cand, + Err(e) => return vec![e], + }; + + // for each entry in the DB (live). + for e in all_cand { + // create new map + let mo_set: BTreeMap = BTreeMap::new(); + // searcch direct memberships of live groups. + let filt_in: Filter = + Filter::new_ignore_hidden(Filter::Eq("member".to_string(), e.get_uuid().clone())); + + let direct_memberof = match qs + .internal_search(au, filt_in) + .map_err(|_| ConsistencyError::QueryServerSearchFailure) + { + Ok(d_mo) => d_mo, + Err(e) => return vec![Err(e)], + }; + // for all direct -> add uuid to map + + let d_groups_set: BTreeMap<&String, ()> = + direct_memberof.iter().map(|e| (e.get_uuid(), ())).collect(); + + audit_log!(au, "Direct groups {:?} -> {:?}", e.get_uuid(), d_groups_set); + + let dmos = match e.get_ava(&"directmemberof".to_string()) { + // Avoid a reference issue to return empty set + Some(dmos) => dmos.clone(), + None => { + // No memberof, return empty set. + Vec::new() + } + }; + + audit_log!(au, "DMO groups {:?} -> {:?}", e.get_uuid(), dmos); + + if dmos.len() != direct_memberof.len() { + audit_log!( + au, + "direct set and mo set differ in size: {:?}", + e.get_uuid() + ); + r.push(Err(ConsistencyError::MemberOfInvalid(e.get_id()))); + // Next entry + continue; + }; + + for mo_uuid in dmos { + if !d_groups_set.contains_key(&mo_uuid) { + audit_log!( + au, + "Entry {:?}, MO {:?} not in direct groups", + e.get_uuid(), + mo_uuid + ); + r.push(Err(ConsistencyError::MemberOfInvalid(e.get_id()))); + // Next entry + continue; + } + } + + // Could check all dmos in mos? + + /* To check nested! */ + // add all direct to a stack + // for all in stack + // check their direct memberships + // if not in map + // add to map + // push to stack + + // check mo == map set + // if not, consistency error! + } + + r + } +} + +#[cfg(test)] +mod tests { + // #[macro_use] + // use crate::plugins::Plugin; + use crate::entry::{Entry, EntryInvalid, EntryNew}; + // use crate::error::OperationError; + use crate::filter::Filter; + use crate::modify::{Modify, ModifyList}; + use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction}; + + static EA: &'static str = r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["group"], + "name": ["testgroup_a"], + "uuid": ["aaaaaaaa-f82e-4484-a407-181aa03bda5c"] + } + }"#; + + static UUID_A: &'static str = "aaaaaaaa-f82e-4484-a407-181aa03bda5c"; + + static EB: &'static str = r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["group"], + "name": ["testgroup_b"], + "uuid": ["bbbbbbbb-2438-4384-9891-48f4c8172e9b"] + } + }"#; + + static UUID_B: &'static str = "bbbbbbbb-2438-4384-9891-48f4c8172e9b"; + + static EC: &'static str = r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["group"], + "name": ["testgroup_c"], + "uuid": ["cccccccc-9b01-423f-9ba6-51aa4bbd5dd2"] + } + }"#; + + static UUID_C: &'static str = "cccccccc-9b01-423f-9ba6-51aa4bbd5dd2"; + + static ED: &'static str = r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["group"], + "name": ["testgroup_d"], + "uuid": ["dddddddd-2ab3-48e3-938d-1b4754cd2984"] + } + }"#; + + static UUID_D: &'static str = "dddddddd-2ab3-48e3-938d-1b4754cd2984"; + + macro_rules! assert_memberof_int { + ( + $au:expr, + $qs:expr, + $ea:expr, + $eb:expr, + $mo:expr, + $cand:expr + ) => {{ + let filt = Filter::And(vec![ + // Assert EA + Filter::Eq("uuid".to_string(), $ea.to_string()), + // is memberof EB + Filter::Eq($mo.to_string(), $eb.to_string()), + ]); + let cands = $qs + .internal_search($au, filt) + .expect("Internal search failure"); + println!("{:?}", cands); + assert!(cands.len() == $cand); + }}; + } + + macro_rules! assert_memberof { + ( + $au:expr, + $qs:expr, + $ea:expr, + $eb:expr + ) => {{ + assert_memberof_int!($au, $qs, $ea, $eb, "memberof", 1); + }}; + } + + macro_rules! assert_dirmemberof { + ( + $au:expr, + $qs:expr, + $ea:expr, + $eb:expr + ) => {{ + assert_memberof_int!($au, $qs, $ea, $eb, "directmemberof", 1); + }}; + } + + macro_rules! assert_not_memberof { + ( + $au:expr, + $qs:expr, + $ea:expr, + $eb:expr + ) => {{ + assert_memberof_int!($au, $qs, $ea, $eb, "memberof", 0); + }}; + } + + macro_rules! assert_not_dirmemberof { + ( + $au:expr, + $qs:expr, + $ea:expr, + $eb:expr + ) => {{ + assert_memberof_int!($au, $qs, $ea, $eb, "directmemberof", 0); + }}; + } + + #[test] + fn test_create_mo_single() { + // A -> B + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + + let preload = Vec::new(); + let create = vec![ea, eb]; + run_create_test!( + Ok(()), + preload, + create, + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + } + ); + } + + #[test] + fn test_create_mo_nested() { + // A -> B -> C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + + let preload = Vec::new(); + let create = vec![ea, eb, ec]; + run_create_test!( + Ok(()), + preload, + create, + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + // This is due to nestig, C should be MO both! + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_create_mo_cycle() { + // A -> B -> C - + // ^-----------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + ec.add_ava("member".to_string(), UUID_A.to_string()); + + let preload = Vec::new(); + let create = vec![ea, eb, ec]; + run_create_test!( + Ok(()), + preload, + create, + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_memberof!(au, qs, UUID_A, UUID_A); + assert_memberof!(au, qs, UUID_A, UUID_B); + assert_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_memberof!(au, qs, UUID_B, UUID_B); + assert_memberof!(au, qs, UUID_B, UUID_C); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_create_mo_multi_cycle() { + // A -> B -> C --> D - + // ^-----------/ / + // |---------------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + let mut ed: Entry = + serde_json::from_str(ED).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + + ec.add_ava("member".to_string(), UUID_A.to_string()); + ec.add_ava("member".to_string(), UUID_D.to_string()); + + ed.add_ava("member".to_string(), UUID_A.to_string()); + + let preload = Vec::new(); + let create = vec![ea, eb, ec, ed]; + run_create_test!( + Ok(()), + preload, + create, + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_memberof!(au, qs, UUID_A, UUID_A); + assert_memberof!(au, qs, UUID_A, UUID_B); + assert_memberof!(au, qs, UUID_A, UUID_C); + assert_memberof!(au, qs, UUID_A, UUID_D); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_memberof!(au, qs, UUID_B, UUID_B); + assert_memberof!(au, qs, UUID_B, UUID_C); + assert_memberof!(au, qs, UUID_B, UUID_D); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_memberof!(au, qs, UUID_C, UUID_C); + assert_memberof!(au, qs, UUID_C, UUID_D); + + assert_memberof!(au, qs, UUID_D, UUID_A); + assert_memberof!(au, qs, UUID_D, UUID_B); + assert_memberof!(au, qs, UUID_D, UUID_C); + assert_memberof!(au, qs, UUID_D, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_dirmemberof!(au, qs, UUID_A, UUID_C); + assert_dirmemberof!(au, qs, UUID_A, UUID_D); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_D, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_B); + assert_dirmemberof!(au, qs, UUID_D, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_D); + } + ); + } + + #[test] + fn test_modify_mo_add_simple() { + // A B + // Add member + // A -> B + let ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let preload = vec![ea, eb]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + ModifyList::new_list(vec![Modify::Present( + "member".to_string(), + UUID_B.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + } + ); + } + + #[test] + fn test_modify_mo_add_nested_1() { + // A B -> C + // Add member A -> B + // A -> B -> C + let ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + + let preload = vec![ea, eb, ec]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + ModifyList::new_list(vec![Modify::Present( + "member".to_string(), + UUID_B.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_modify_mo_add_nested_2() { + // A -> B C + // Add member B -> C + // A -> B -> C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + + let preload = vec![ea, eb, ec]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_B.to_string()), + ModifyList::new_list(vec![Modify::Present( + "member".to_string(), + UUID_C.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_modify_mo_add_cycle() { + // A -> B -> C + // + // Add member C -> A + // A -> B -> C - + // ^-----------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + + let preload = vec![ea, eb, ec]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_C.to_string()), + ModifyList::new_list(vec![Modify::Present( + "member".to_string(), + UUID_A.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_memberof!(au, qs, UUID_A, UUID_A); + assert_memberof!(au, qs, UUID_A, UUID_B); + assert_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_memberof!(au, qs, UUID_B, UUID_B); + assert_memberof!(au, qs, UUID_B, UUID_C); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_modify_mo_add_multi_cycle() { + // A -> B -> C --> D + // + // Add member C -> A + // Add member C -> D + // Add member D -> A + // + // A -> B -> C --> D - + // ^-----------/ / + // |---------------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + let ed: Entry = + serde_json::from_str(ED).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + ec.add_ava("member".to_string(), UUID_D.to_string()); + + let preload = vec![ea, eb, ec, ed]; + run_modify_test!( + Ok(()), + preload, + Filter::Or(vec![ + Filter::Eq("uuid".to_string(), UUID_C.to_string()), + Filter::Eq("uuid".to_string(), UUID_D.to_string()), + ]), + ModifyList::new_list(vec![Modify::Present( + "member".to_string(), + UUID_A.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_memberof!(au, qs, UUID_A, UUID_A); + assert_memberof!(au, qs, UUID_A, UUID_B); + assert_memberof!(au, qs, UUID_A, UUID_C); + assert_memberof!(au, qs, UUID_A, UUID_D); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_memberof!(au, qs, UUID_B, UUID_B); + assert_memberof!(au, qs, UUID_B, UUID_C); + assert_memberof!(au, qs, UUID_B, UUID_D); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_memberof!(au, qs, UUID_C, UUID_C); + assert_memberof!(au, qs, UUID_C, UUID_D); + + assert_memberof!(au, qs, UUID_D, UUID_A); + assert_memberof!(au, qs, UUID_D, UUID_B); + assert_memberof!(au, qs, UUID_D, UUID_C); + assert_memberof!(au, qs, UUID_D, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_dirmemberof!(au, qs, UUID_A, UUID_C); + assert_dirmemberof!(au, qs, UUID_A, UUID_D); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_D, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_B); + assert_dirmemberof!(au, qs, UUID_D, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_D); + } + ); + } + + #[test] + fn test_modify_mo_del_simple() { + // A -> B + // remove member A -> B + // A B + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + + let preload = vec![ea, eb]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + ModifyList::new_list(vec![Modify::Removed( + "member".to_string(), + UUID_B.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + + assert_not_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + } + ); + } + + #[test] + fn test_modify_mo_del_nested_1() { + // A -> B -> C + // Remove A -> B + // A B -> C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + + let preload = vec![ea, eb, ec]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + ModifyList::new_list(vec![Modify::Removed( + "member".to_string(), + UUID_B.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_not_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_not_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_modify_mo_del_nested_2() { + // A -> B -> C + // Remove B -> C + // A -> B C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + eb.add_ava("member".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + + let preload = vec![ea, eb, ec]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_B.to_string()), + ModifyList::new_list(vec![Modify::Removed( + "member".to_string(), + UUID_C.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_not_memberof!(au, qs, UUID_C, UUID_A); + assert_not_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_modify_mo_del_cycle() { + // A -> B -> C - + // ^-----------/ + // Remove C -> A + // A -> B -> C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_C.to_string()); + ea.add_ava("memberof".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_A.to_string()); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + + ec.add_ava("member".to_string(), UUID_A.to_string()); + ec.add_ava("memberof".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + + let preload = vec![ea, eb, ec]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_C.to_string()), + ModifyList::new_list(vec![Modify::Removed( + "member".to_string(), + UUID_A.to_string() + )]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_modify_mo_del_multi_cycle() { + // A -> B -> C --> D - + // ^-----------/ / + // |---------------/ + // + // Remove C -> D + // Remove C -> A + // + // A -> B -> C D - + // ^ / + // |---------------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + let mut ed: Entry = + serde_json::from_str(ED).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_D.to_string()); + ea.add_ava("memberof".to_string(), UUID_C.to_string()); + ea.add_ava("memberof".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_A.to_string()); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_D.to_string()); + eb.add_ava("memberof".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + + ec.add_ava("member".to_string(), UUID_A.to_string()); + ec.add_ava("member".to_string(), UUID_D.to_string()); + ec.add_ava("memberof".to_string(), UUID_D.to_string()); + ec.add_ava("memberof".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + + ed.add_ava("member".to_string(), UUID_A.to_string()); + ed.add_ava("memberof".to_string(), UUID_D.to_string()); + ed.add_ava("memberof".to_string(), UUID_C.to_string()); + ed.add_ava("memberof".to_string(), UUID_B.to_string()); + ed.add_ava("memberof".to_string(), UUID_A.to_string()); + + let preload = vec![ea, eb, ec, ed]; + run_modify_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_C.to_string()), + ModifyList::new_list(vec![ + Modify::Removed("member".to_string(), UUID_A.to_string()), + Modify::Removed("member".to_string(), UUID_D.to_string()), + ]), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + assert_memberof!(au, qs, UUID_A, UUID_D); + + assert_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + assert_memberof!(au, qs, UUID_B, UUID_D); + + assert_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + assert_memberof!(au, qs, UUID_C, UUID_D); + + assert_not_memberof!(au, qs, UUID_D, UUID_A); + assert_not_memberof!(au, qs, UUID_D, UUID_B); + assert_not_memberof!(au, qs, UUID_D, UUID_C); + assert_not_memberof!(au, qs, UUID_D, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + assert_dirmemberof!(au, qs, UUID_A, UUID_D); + + assert_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_D, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_D); + } + ); + } + + #[test] + fn test_delete_mo_simple() { + // X -> B + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + + let preload = vec![ea, eb]; + run_delete_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + + assert_not_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + } + ); + } + + #[test] + fn test_delete_mo_nested_head() { + // X -> B -> C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + + let preload = vec![ea, eb, ec]; + run_delete_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_not_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_delete_mo_nested_branch() { + // A -> X -> C + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + + let preload = vec![ea, eb, ec]; + run_delete_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_B.to_string()), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_C); + + assert_not_memberof!(au, qs, UUID_C, UUID_A); + assert_not_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_delete_mo_cycle() { + // X -> B -> C - + // ^-----------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_A.to_string()); + ea.add_ava("memberof".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_C.to_string()); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + eb.add_ava("memberof".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_C.to_string()); + + ec.add_ava("member".to_string(), UUID_A.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + ec.add_ava("memberof".to_string(), UUID_C.to_string()); + + let preload = vec![ea, eb, ec]; + run_delete_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_A.to_string()), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_B, UUID_A); + assert_not_memberof!(au, qs, UUID_B, UUID_B); + assert_not_memberof!(au, qs, UUID_B, UUID_C); + + assert_not_memberof!(au, qs, UUID_C, UUID_A); + assert_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_B, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_B, UUID_C); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + } + ); + } + + #[test] + fn test_delete_mo_multi_cycle() { + // A -> X -> C --> D - + // ^-----------/ / + // |---------------/ + let mut ea: Entry = + serde_json::from_str(EA).expect("Json parse failure"); + + let mut eb: Entry = + serde_json::from_str(EB).expect("Json parse failure"); + + let mut ec: Entry = + serde_json::from_str(EC).expect("Json parse failure"); + + let mut ed: Entry = + serde_json::from_str(ED).expect("Json parse failure"); + + ea.add_ava("member".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_A.to_string()); + ea.add_ava("memberof".to_string(), UUID_B.to_string()); + ea.add_ava("memberof".to_string(), UUID_C.to_string()); + ea.add_ava("memberof".to_string(), UUID_D.to_string()); + + eb.add_ava("member".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_A.to_string()); + eb.add_ava("memberof".to_string(), UUID_B.to_string()); + eb.add_ava("memberof".to_string(), UUID_C.to_string()); + eb.add_ava("memberof".to_string(), UUID_D.to_string()); + + ec.add_ava("member".to_string(), UUID_A.to_string()); + ec.add_ava("member".to_string(), UUID_D.to_string()); + ec.add_ava("memberof".to_string(), UUID_A.to_string()); + ec.add_ava("memberof".to_string(), UUID_B.to_string()); + ec.add_ava("memberof".to_string(), UUID_C.to_string()); + ec.add_ava("memberof".to_string(), UUID_D.to_string()); + + ed.add_ava("member".to_string(), UUID_A.to_string()); + ed.add_ava("memberof".to_string(), UUID_A.to_string()); + ed.add_ava("memberof".to_string(), UUID_B.to_string()); + ed.add_ava("memberof".to_string(), UUID_C.to_string()); + ed.add_ava("memberof".to_string(), UUID_D.to_string()); + + let preload = vec![ea, eb, ec, ed]; + run_delete_test!( + Ok(()), + preload, + Filter::Eq("uuid".to_string(), UUID_B.to_string()), + false, + |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { + // V-- this uuid is + // V-- memberof this UUID + assert_not_memberof!(au, qs, UUID_A, UUID_B); + assert_not_memberof!(au, qs, UUID_A, UUID_A); + assert_memberof!(au, qs, UUID_A, UUID_C); + assert_memberof!(au, qs, UUID_A, UUID_D); + + assert_not_memberof!(au, qs, UUID_C, UUID_A); + assert_not_memberof!(au, qs, UUID_C, UUID_B); + assert_not_memberof!(au, qs, UUID_C, UUID_C); + assert_not_memberof!(au, qs, UUID_C, UUID_D); + + assert_not_memberof!(au, qs, UUID_D, UUID_A); + assert_not_memberof!(au, qs, UUID_D, UUID_B); + assert_memberof!(au, qs, UUID_D, UUID_C); + assert_not_memberof!(au, qs, UUID_D, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_A, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_A, UUID_B); + assert_dirmemberof!(au, qs, UUID_A, UUID_C); + assert_dirmemberof!(au, qs, UUID_A, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_C, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_D); + + assert_not_dirmemberof!(au, qs, UUID_D, UUID_A); + assert_not_dirmemberof!(au, qs, UUID_C, UUID_B); + assert_dirmemberof!(au, qs, UUID_D, UUID_C); + assert_not_dirmemberof!(au, qs, UUID_D, UUID_D); + } + ); + } +} diff --git a/src/lib/plugins/mod.rs b/src/lib/plugins/mod.rs index 72e2b67f6..2b7000b48 100644 --- a/src/lib/plugins/mod.rs +++ b/src/lib/plugins/mod.rs @@ -10,6 +10,7 @@ mod macros; mod base; mod failure; +mod memberof; mod protected; mod recycle; mod refint; @@ -64,6 +65,7 @@ trait Plugin { _au: &mut AuditScope, _qs: &QueryServerWriteTransaction, // List of what we modified that was valid? + _pre_cand: &Vec>, _cand: &Vec>, _ce: &ModifyEvent, _modlist: &ModifyList, @@ -190,6 +192,7 @@ macro_rules! run_post_modify_plugin { ( $au:ident, $qs:ident, + $pre_cand:ident, $cand:ident, $ce:ident, $ml:ident, @@ -199,6 +202,7 @@ macro_rules! run_post_modify_plugin { let r = audit_segment!(audit_scope, || <($target_plugin)>::post_modify( &mut audit_scope, $qs, + $pre_cand, $cand, $ce, $ml @@ -308,6 +312,7 @@ impl Plugins { audit_segment!(au, || { let res = run_post_create_plugin!(au, qs, cand, ce, base::Base).and_then(|_| { run_post_create_plugin!(au, qs, cand, ce, refint::ReferentialIntegrity) + .and_then(|_| run_post_create_plugin!(au, qs, cand, ce, memberof::MemberOf)) }); res @@ -334,14 +339,34 @@ impl Plugins { pub fn run_post_modify( au: &mut AuditScope, qs: &QueryServerWriteTransaction, + pre_cand: &Vec>, cand: &Vec>, me: &ModifyEvent, modlist: &ModifyList, ) -> Result<(), OperationError> { audit_segment!(au, || { - let res = - run_post_modify_plugin!(au, qs, cand, me, modlist, base::Base).and_then(|_| { - run_post_modify_plugin!(au, qs, cand, me, modlist, refint::ReferentialIntegrity) + let res = run_post_modify_plugin!(au, qs, pre_cand, cand, me, modlist, base::Base) + .and_then(|_| { + run_post_modify_plugin!( + au, + qs, + pre_cand, + cand, + me, + modlist, + refint::ReferentialIntegrity + ) + .and_then(|_| { + run_post_modify_plugin!( + au, + qs, + pre_cand, + cand, + me, + modlist, + memberof::MemberOf + ) + }) }); res @@ -372,6 +397,7 @@ impl Plugins { audit_segment!(au, || { let res = run_post_delete_plugin!(au, qs, cand, de, base::Base).and_then(|_| { run_post_delete_plugin!(au, qs, cand, de, refint::ReferentialIntegrity) + .and_then(|_| run_post_delete_plugin!(au, qs, cand, de, memberof::MemberOf)) }); res @@ -385,6 +411,7 @@ impl Plugins { let mut results = Vec::new(); run_verify_plugin!(au, qs, &mut results, base::Base); run_verify_plugin!(au, qs, &mut results, refint::ReferentialIntegrity); + run_verify_plugin!(au, qs, &mut results, memberof::MemberOf); results } } diff --git a/src/lib/plugins/refint.rs b/src/lib/plugins/refint.rs index 5d3f028ad..9b96eeb32 100644 --- a/src/lib/plugins/refint.rs +++ b/src/lib/plugins/refint.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use crate::audit::AuditScope; -use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; +use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid}; use crate::error::{ConsistencyError, OperationError}; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; use crate::filter::{Filter, FilterInvalid}; @@ -30,12 +30,13 @@ impl ReferentialIntegrity { fn check_uuid_exists( au: &mut AuditScope, qs: &QueryServerWriteTransaction, + rtype: &String, uuid: &String, ) -> Result<(), OperationError> { let mut au_qs = AuditScope::new("qs_exist"); let filt_in: Filter = Filter::new_ignore_hidden(Filter::Eq("uuid".to_string(), uuid.clone())); - let r = qs.internal_exists(au, filt_in); + let r = qs.internal_exists(&mut au_qs, filt_in); au.append_scope(au_qs); let b = try_audit!(au, r); @@ -43,6 +44,12 @@ impl ReferentialIntegrity { if b { Ok(()) } else { + audit_log!( + au, + "{:?}:{:?} UUID reference not found in database", + rtype, + uuid + ); Err(OperationError::Plugin) } } @@ -85,7 +92,7 @@ impl Plugin for ReferentialIntegrity { Some(vs) => { // For each value in the set. for v in vs { - Self::check_uuid_exists(au, qs, v)? + Self::check_uuid_exists(au, qs, &rtype.name, v)? } } None => {} @@ -98,8 +105,9 @@ impl Plugin for ReferentialIntegrity { fn post_modify( au: &mut AuditScope, qs: &QueryServerWriteTransaction, + _pre_cand: &Vec>, _cand: &Vec>, - me: &ModifyEvent, + _me: &ModifyEvent, modlist: &ModifyList, ) -> Result<(), OperationError> { let schema = qs.get_schema(); @@ -113,7 +121,7 @@ impl Plugin for ReferentialIntegrity { match ref_types.get(a) { Some(a_type) => { // So it is a reference type, now check it. - Self::check_uuid_exists(au, qs, v)? + Self::check_uuid_exists(au, qs, &a_type.name, v)? } None => {} } @@ -148,7 +156,8 @@ impl Plugin for ReferentialIntegrity { .collect(); // Generate a filter which is the set of all schema reference types - // as EQ to all uuid of all entries in delete. + // as EQ to all uuid of all entries in delete. - this INCLUDES recycled + // types too! let filt: Filter = Filter::Or( uuids .iter() @@ -256,8 +265,8 @@ impl Plugin for ReferentialIntegrity { #[cfg(test)] mod tests { - #[macro_use] - use crate::plugins::Plugin; + // #[macro_use] + // use crate::plugins::Plugin; use crate::entry::{Entry, EntryInvalid, EntryNew}; use crate::error::OperationError; use crate::filter::Filter; @@ -338,7 +347,7 @@ mod tests { Filter::Eq("name".to_string(), "testgroup_b".to_string()), ) .expect("Internal search failure"); - let ue = cands.first().expect("No cand"); + let _ue = cands.first().expect("No cand"); } ); } @@ -374,7 +383,7 @@ mod tests { let cands = qs .internal_search(au, Filter::Eq("name".to_string(), "testgroup".to_string())) .expect("Internal search failure"); - let ue = cands.first().expect("No cand"); + let _ue = cands.first().expect("No cand"); } ); } @@ -615,7 +624,7 @@ mod tests { preload, Filter::Eq("name".to_string(), "testgroup_a".to_string()), false, - |au: &mut AuditScope, qs: &QueryServerWriteTransaction| {} + |_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {} ); } @@ -663,7 +672,7 @@ mod tests { preload, Filter::Eq("name".to_string(), "testgroup_b".to_string()), false, - |au: &mut AuditScope, qs: &QueryServerWriteTransaction| {} + |_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {} ); } @@ -692,7 +701,7 @@ mod tests { preload, Filter::Eq("name".to_string(), "testgroup_b".to_string()), false, - |au: &mut AuditScope, qs: &QueryServerWriteTransaction| {} + |_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {} ); } } diff --git a/src/lib/proto_v1_actors.rs b/src/lib/proto_v1_actors.rs index 10d2b6100..2eb4e5b83 100644 --- a/src/lib/proto_v1_actors.rs +++ b/src/lib/proto_v1_actors.rs @@ -6,8 +6,8 @@ use crate::be::Backend; use crate::error::OperationError; use crate::event::{ - CreateEvent, DeleteEvent, ModifyEvent, OpResult, PurgeRecycledEvent, PurgeTombstoneEvent, - SearchEvent, SearchResult, + CreateEvent, DeleteEvent, ModifyEvent, PurgeRecycledEvent, PurgeTombstoneEvent, SearchEvent, + SearchResult, }; use crate::log::EventLog; use crate::schema::{Schema, SchemaReadTransaction}; @@ -275,7 +275,7 @@ impl Handler for QueryServerV1 { let res = qs_write .purge_tombstones(&mut audit) - .map(|_| qs_write.commit(&mut audit).map(|_| OpResult {})); + .and_then(|_| qs_write.commit(&mut audit)); audit_log!(audit, "Purge tombstones result: {:?}", res); res.expect("Invalid Server State"); }); @@ -296,7 +296,7 @@ impl Handler for QueryServerV1 { let res = qs_write .purge_recycled(&mut audit) - .map(|_| qs_write.commit(&mut audit).map(|_| OpResult {})); + .and_then(|_| qs_write.commit(&mut audit)); audit_log!(audit, "Purge recycled result: {:?}", res); res.expect("Invalid Server State"); }); diff --git a/src/lib/schema.rs b/src/lib/schema.rs index c53632dae..33ea9102e 100644 --- a/src/lib/schema.rs +++ b/src/lib/schema.rs @@ -733,6 +733,20 @@ impl SchemaInner { syntax: SyntaxType::REFERENCE_UUID, }, ); + self.attributes.insert( + String::from("directmemberof"), + SchemaAttribute { + name: String::from("directmemberof"), + uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_DIRECTMEMBEROF) + .expect("unable to parse static uuid"), + description: String::from("reverse direct group membership of the object"), + system: true, + secret: false, + multivalue: true, + index: vec![IndexType::EQUALITY], + syntax: SyntaxType::REFERENCE_UUID, + }, + ); // ssh_publickey // multi self.attributes.insert( String::from("ssh_publickey"), diff --git a/src/lib/server.rs b/src/lib/server.rs index 058eeec1e..e6f83bfa7 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -609,12 +609,14 @@ impl<'a> QueryServerWriteTransaction<'a> { Err(e) => return Err(OperationError::SchemaViolation(e)), }; - let mut candidates: Result>, _> = pre_candidates - .into_iter() - .map(|er| er.invalidate().apply_modlist(&modlist)) + let mut candidates: Vec> = pre_candidates + .iter() + .map(|er| er.clone().invalidate()) .collect(); - let mut candidates = try_audit!(au, candidates); + candidates + .iter_mut() + .for_each(|er| er.apply_modlist(&modlist)); audit_log!(au, "delete: candidates -> {:?}", candidates); @@ -807,12 +809,16 @@ impl<'a> QueryServerWriteTransaction<'a> { // Clone a set of writeables. // Apply the modlist -> Remember, we have a set of origs // and the new modified ents. - let mut candidates: Result>, _> = pre_candidates - .into_iter() - .map(|er| er.invalidate().apply_modlist(&modlist)) + let mut candidates: Vec> = pre_candidates + .iter() + .map(|er| er.clone().invalidate()) .collect(); - let mut candidates = try_audit!(au, candidates); + candidates + .iter_mut() + .for_each(|er| er.apply_modlist(&modlist)); + + // let mut candidates = try_audit!(au, candidates); audit_log!(au, "modify: candidates -> {:?}", candidates); @@ -833,6 +839,9 @@ impl<'a> QueryServerWriteTransaction<'a> { // optimisations, this could be premature - so we for now, just // do the CORRECT thing and recommit as we may find later we always // want to add CSN's or other. + // + // memberOf actually wants the pre cand list and the norm_cand list to see what + // changed. Could be optimised, but this is correct still ... let res: Result>, SchemaError> = candidates .into_iter() @@ -860,8 +869,14 @@ impl<'a> QueryServerWriteTransaction<'a> { // Post Plugins let mut audit_plugin_post = AuditScope::new("plugin_post_modify"); - let plug_post_res = - Plugins::run_post_modify(&mut audit_plugin_post, &self, &norm_cand, me, &modlist); + let plug_post_res = Plugins::run_post_modify( + &mut audit_plugin_post, + &self, + &pre_candidates, + &norm_cand, + me, + &modlist, + ); au.append_scope(audit_plugin_post); if plug_post_res.is_err() { @@ -1127,9 +1142,7 @@ mod tests { use crate::proto_v1::Filter as ProtoFilter; use crate::proto_v1::Modify as ProtoModify; use crate::proto_v1::ModifyList as ProtoModifyList; - use crate::proto_v1::{ - DeleteRequest, ModifyRequest, ReviveRecycledRequest, SearchRecycledRequest, SearchRequest, - }; + use crate::proto_v1::{DeleteRequest, ModifyRequest, ReviveRecycledRequest}; use crate::schema::Schema; use crate::server::{QueryServer, QueryServerReadTransaction}; diff --git a/src/server/main.rs b/src/server/main.rs index 67b609c60..d22c8a9c8 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -1,6 +1,8 @@ extern crate actix; extern crate rsidm; + + use rsidm::config::Configuration; use rsidm::core::create_server_core; diff --git a/tests/proto_v1_test.rs b/tests/proto_v1_test.rs index a6a0d6325..98b52e4ca 100644 --- a/tests/proto_v1_test.rs +++ b/tests/proto_v1_test.rs @@ -4,13 +4,13 @@ use actix::prelude::*; extern crate rsidm; use rsidm::config::Configuration; use rsidm::core::create_server_core; -use rsidm::proto_v1::{CreateRequest, Entry, OperationResponse, SearchRequest, SearchResponse}; +use rsidm::proto_v1::{CreateRequest, Entry, OperationResponse}; extern crate reqwest; extern crate futures; -use futures::future; -use futures::future::Future; +// use futures::future; +// use futures::future::Future; use std::sync::mpsc; use std::thread;