diff --git a/src/lib/audit.rs b/src/lib/audit.rs index ed376b21f..ff7fbfd6b 100644 --- a/src/lib/audit.rs +++ b/src/lib/audit.rs @@ -12,7 +12,7 @@ macro_rules! audit_log { ($audit:expr, $($arg:tt)*) => ({ use std::fmt; if cfg!(test) || cfg!(debug_assertions) { - print!("DEBUG AUDIT -> "); + print!("DEBUG AUDIT ({})-> ", $audit.id()); println!($($arg)*) } $audit.log_event( @@ -101,6 +101,10 @@ impl AuditScope { } } + pub fn id(&self) -> &str { + self.name.as_str() + } + // Given a new audit event, append it in. pub fn append_scope(&mut self, scope: AuditScope) { self.events.push(AuditEvent::scope(scope)) diff --git a/src/lib/plugins/mod.rs b/src/lib/plugins/mod.rs index b4a4b5a0c..10c0de2a6 100644 --- a/src/lib/plugins/mod.rs +++ b/src/lib/plugins/mod.rs @@ -5,7 +5,11 @@ use error::OperationError; use event::CreateEvent; use schema::Schema; +mod uuid; + trait Plugin { + fn id() -> &'static str; + fn pre_create( be: &mut Backend, au: &mut AuditScope, @@ -45,7 +49,48 @@ trait Plugin { } } -mod uuid; +pub struct Plugins{} + +macro_rules! run_pre_create_plugin { + ( + $be:ident, + $au:ident, + $cand:ident, + $ce:ident, + $schema:ident, + $target_plugin:ty + ) => {{ + let mut audit_scope = AuditScope::new(<($target_plugin)>::id()); + let r = audit_segment!(audit_scope, || { + <($target_plugin)>::pre_create( + $be, &mut audit_scope, $cand, $ce, $schema + ) + }); + $au.append_scope(audit_scope); + r + }} +} + +impl Plugins { + pub fn run_pre_create( + be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema, + ) -> Result<(), OperationError> { + audit_segment!(audit_plugin_pre, || { + + // map chain? + let uuid_res = run_pre_create_plugin!(be, au, cand, ce, schema, uuid::UUID); + + + // TODO, actually return the right thing ... + uuid_res + }) + } + +} // We should define the order that plugins should run diff --git a/src/lib/plugins/uuid.rs b/src/lib/plugins/uuid.rs index a281e786e..8ff27e8bf 100644 --- a/src/lib/plugins/uuid.rs +++ b/src/lib/plugins/uuid.rs @@ -9,9 +9,12 @@ use event::CreateEvent; use filter::Filter; use schema::Schema; -struct UUID {} +pub struct UUID {} impl Plugin for UUID { + fn id() -> &'static str { + "UUID" + } // Need to be given the backend(for testing ease) // audit // the mut set of entries to create @@ -30,17 +33,36 @@ impl Plugin for UUID { for entry in cand.iter_mut() { let name_uuid = String::from("uuid"); + audit_log!(au, "UUID check on entry: {:?}", entry); + // if they don't have uuid, create it. // TODO: get_ava should have a str version for effeciency? let mut c_uuid = match entry.get_ava(&name_uuid) { Some(u) => { // Actually check we have a value, could be empty array ... - let v = u.first().unwrap(); + if u.len() > 1 { + audit_log!(au, "Entry defines uuid attr, but multiple values."); + return Err(OperationError::Plugin) + }; + + let v = match u.first() { + Some(v) => v, + None => { + audit_log!(au, "Entry defines uuid attr, but no value."); + return Err(OperationError::Plugin) + } + }; + // This could actually fail, so we probably need to handle // this better .... + // TODO: Make this a SCHEMA check, not a manual one. + // match Uuid::parse_str(v.as_str()) { Ok(up) => up, - Err(_) => return Err(OperationError::Plugin), + Err(_) => { + audit_log!(au, "Entry contains invalid UUID content, rejecting out of principle."); + return Err(OperationError::Plugin) + } } } None => Uuid::new_v4(), @@ -48,7 +70,6 @@ impl Plugin for UUID { // Make it a string, so we can filter. let str_uuid = format!("{}", c_uuid); - println!("{}", str_uuid); let mut au_be = AuditScope::new("be_exist"); @@ -58,27 +79,25 @@ impl Plugin for UUID { let r = be.exists(&mut au_be, &filt); au.append_scope(au_be); - // end the scope? + // end the scope for the be operation. match r { Ok(b) => { if b == true { + audit_log!(au, "UUID already exists, rejecting."); return Err(OperationError::Plugin); } } - Err(e) => return Err(OperationError::Plugin), + Err(e) => { + audit_log!(au, "Backend error occured checking UUID existance."); + return Err(OperationError::Plugin) + } } - // check that the uuid is unique in the be (even if one is provided - // we especially need to check that) - - // if not unique, generate another, and try again. - - // If it's okay, now put it into the entry. - // we may need to inject the base OC required for all objects in our - // server to support this? let str_uuid = format!("{}", c_uuid); + audit_log!(au, "Set UUID {} to entry", str_uuid); let ava_uuid: Vec = vec![str_uuid]; + entry.set_avas(name_uuid, ava_uuid); } // done! @@ -142,9 +161,6 @@ mod tests { // Check empty create #[test] fn test_pre_create_empty() { - // Need a macro to create all the bits here ... - // Macro needs preload entries, the create entries - // schema, identity for create event (later) let preload: Vec = Vec::new(); let mut create: Vec = Vec::new(); run_pre_create_test!( @@ -169,9 +185,6 @@ mod tests { // check create where no uuid #[test] fn test_pre_create_no_uuid() { - // Need a macro to create all the bits here ... - // Macro needs preload entries, the create entries - // schema, identity for create event (later) let preload: Vec = Vec::new(); let e: Entry = serde_json::from_str( @@ -208,16 +221,81 @@ mod tests { } // check unparseable uuid + #[test] + fn test_pre_create_uuid_invalid() { + let preload: Vec = Vec::new(); + + let e: Entry = serde_json::from_str( + r#"{ + "attrs": { + "class": ["person"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"], + "uuid": ["xxxxxx"] + } + }"#, + ) + .unwrap(); + + let mut create = vec![e.clone()]; + + run_pre_create_test!( + preload, + create, + false, + false, + |be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema| { + let r = UUID::pre_create(be, au, cand, ce, schema); + assert!(r.is_err()); + } + ); + } + // check entry where uuid is empty list + #[test] + fn test_pre_create_uuid_empty() { + let preload: Vec = Vec::new(); + + let e: Entry = serde_json::from_str( + r#"{ + "attrs": { + "class": ["person"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"], + "uuid": [] + } + }"#, + ) + .unwrap(); + + let mut create = vec![e.clone()]; + + run_pre_create_test!( + preload, + create, + false, + false, + |be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema| { + let r = UUID::pre_create(be, au, cand, ce, schema); + assert!(r.is_err()); + } + ); + } + // check create where provided uuid is valid. It should be unchanged. - - // check create where uuid already exists. #[test] - fn test_pre_create_uuid_exist() { - // Need a macro to create all the bits here ... - // Macro needs preload entries, the create entries - // schema, identity for create event (later) + fn test_pre_create_uuid_valid() { let preload: Vec = Vec::new(); let e: Entry = serde_json::from_str( @@ -234,7 +312,78 @@ mod tests { .unwrap(); let mut create = vec![e.clone()]; - let mut preload = vec![e]; + + run_pre_create_test!( + preload, + create, + false, + false, + |be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema| { + let r = UUID::pre_create(be, au, cand, ce, schema); + assert!(r.is_ok()); + let ue = cand.first().unwrap(); + assert!(ue.attribute_equality("uuid", "79724141-3603-4060-b6bb-35c72772611d")); + } + ); + } + + #[test] + fn test_pre_create_uuid_valid_multi() { + let preload: Vec = Vec::new(); + + let e: Entry = serde_json::from_str( + r#"{ + "attrs": { + "class": ["person"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"], + "uuid": ["79724141-3603-4060-b6bb-35c72772611d", "79724141-3603-4060-b6bb-35c72772611d"] + } + }"#, + ) + .unwrap(); + + let mut create = vec![e.clone()]; + + run_pre_create_test!( + preload, + create, + false, + false, + |be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema| { + let r = UUID::pre_create(be, au, cand, ce, schema); + assert!(r.is_err()); + } + ); + } + + // check create where uuid already exists. + #[test] + fn test_pre_create_uuid_exist() { + let e: Entry = serde_json::from_str( + r#"{ + "attrs": { + "class": ["person"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"], + "uuid": ["79724141-3603-4060-b6bb-35c72772611d"] + } + }"#, + ) + .unwrap(); + + let mut create = vec![e.clone()]; + let preload = vec![e]; run_pre_create_test!( preload, diff --git a/src/lib/schema.rs b/src/lib/schema.rs index 0196f60af..b9ae484c5 100644 --- a/src/lib/schema.rs +++ b/src/lib/schema.rs @@ -668,21 +668,6 @@ impl Schema { .map(|s| (s.clone(), self.attributes.get(s).unwrap())) .collect(); - /* - let may: HashMap = classes - .iter() - // Join our class systemmmust + must into one iter - .flat_map(|(_, cls)| { - cls.systemmust - .iter() - .chain(cls.must.iter()) - .chain(cls.systemmay.iter()) - .chain(cls.may.iter()) - }) - .map(|s| (s.clone(), self.attributes.get(s).unwrap())) - .collect(); - */ - // FIXME: Error needs to say what is missing // We need to return *all* missing attributes. @@ -822,7 +807,9 @@ impl Schema { // Normalise *does not* validate. // Normalise just fixes some possible common issues, but it // can't fix *everything* possibly wrong ... - pub fn normalise_filter(&mut self) {} + pub fn normalise_filter(&mut self) { + unimplemented!() + } } #[cfg(test)] diff --git a/src/lib/server.rs b/src/lib/server.rs index 146cf7749..825b951f0 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -7,7 +7,7 @@ use entry::Entry; use error::OperationError; use event::{CreateEvent, OpResult, SearchEvent, SearchResult}; use log::EventLog; -use plugins; +use plugins::Plugins; use schema::Schema; pub fn start(log: actix::Addr, path: &str, threads: usize) -> actix::Addr { @@ -96,16 +96,26 @@ impl QueryServer { // based on request size in the frontend? // Copy the entries to a writeable form. - let mut candidates: Vec<_> = ce.entries.iter().collect(); + let mut candidates: Vec = ce.entries.iter() + .map(|er| er.clone()) + .collect(); // Start a txn // run any pre plugins, giving them the list of mutable candidates. + // pre-plugins are defined here in their correct order of calling! + // I have no intent to make these dynamic or configurable. - // Run any pre checks - // FIXME: Normalise all entries incoming + let mut audit_plugin_pre = AuditScope::new("plugin_pre_create"); + let plug_pre_res = Plugins::run_pre_create(&mut self.be, &mut audit_plugin_pre, &mut candidates, ce, &self.schema); + au.append_scope(audit_plugin_pre); - let r = ce.entries.iter().fold(Ok(()), |acc, e| { + if plug_pre_res.is_err() { + audit_log!(au, "Create operation failed (plugin), {:?}", plug_pre_res); + return plug_pre_res; + } + + let r = candidates.iter().fold(Ok(()), |acc, e| { if acc.is_ok() { self.schema .validate_entry(e) @@ -115,26 +125,35 @@ impl QueryServer { } }); if r.is_err() { + audit_log!(au, "Create operation failed (schema), {:?}", r); return r; } + // FIXME: Normalise all entries now. + let mut audit_be = AuditScope::new("backend_create"); // We may change from ce.entries later to something else? let res = self .be - .create(&mut audit_be, &ce.entries) + .create(&mut audit_be, &candidates) .map(|_| ()) .map_err(|e| match e { BackendError::EmptyRequest => OperationError::EmptyRequest, _ => OperationError::Backend, }); + au.append_scope(audit_be); + if res.is_err() { + audit_log!(au, "Create operation failed (backend), {:?}", r); + return res; + } // Run any post plugins - // Commit/Abort the txn + // Commit the txn // We are complete, finalise logging and return - au.append_scope(audit_be); + + audit_log!(au, "Create operation success"); res } }