diff --git a/src/clients/whoami.rs b/src/clients/whoami.rs index 390a56c87..b4c376c0e 100644 --- a/src/clients/whoami.rs +++ b/src/clients/whoami.rs @@ -1,4 +1,3 @@ - extern crate rsidm; use rsidm::proto_v1; diff --git a/src/lib/core.rs b/src/lib/core.rs index 0ad6234e6..c5d3265c9 100644 --- a/src/lib/core.rs +++ b/src/lib/core.rs @@ -1,9 +1,9 @@ // use actix::SystemRunner; +use actix_web::middleware::session::{self, RequestSession}; use actix_web::{ error, http, middleware, App, AsyncResponder, Error, FutureResponse, HttpMessage, HttpRequest, - HttpResponse, Path, State, Result, + HttpResponse, Path, Result, State, }; -use actix_web::middleware::session::{self, RequestSession}; use bytes::BytesMut; use futures::{future, Future, Stream}; @@ -11,8 +11,7 @@ use futures::{future, Future, Stream}; use super::config::Configuration; // SearchResult -use super::event::{CreateEvent, SearchEvent, - }; +use super::event::{CreateEvent, SearchEvent}; use super::filter::Filter; use super::log; use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse}; @@ -151,7 +150,6 @@ fn whoami(req: &HttpRequest) -> Result<&'static str> { Ok("welcome!") } - pub fn create_server_core(config: Configuration) { // Configure the middleware logger ::std::env::set_var("RUST_LOG", "actix_web=info"); @@ -179,15 +177,15 @@ pub fn create_server_core(config: Configuration) { // Signed prevents tampering. this 32 byte key MUST // 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 - // Disallow from js - .http_only(true) - .name("rsidm-session") - // This forces https only - .secure(false) + .path("/") + //.max_age() duration of the token life + // .domain() + //.same_site() constraunt to the domain + // Disallow from js + .http_only(true) + .name("rsidm-session") + // This forces https only + .secure(false), )) .resource("/", |r| r.f(index)) // curl --header ...? diff --git a/src/lib/entry.rs b/src/lib/entry.rs index 6476e4e17..fefc8cabb 100644 --- a/src/lib/entry.rs +++ b/src/lib/entry.rs @@ -130,7 +130,8 @@ impl Entry { } // FIXME: Should this collect from iter instead? - pub fn add_avas(&mut self, attr: String, values: Vec) { + /// Overwrite the existing avas. + pub fn set_avas(&mut self, attr: String, values: Vec) { // Overwrite the existing value let _ = self.attrs.insert(attr, values); } diff --git a/src/lib/error.rs b/src/lib/error.rs index 884616190..5da382eec 100644 --- a/src/lib/error.rs +++ b/src/lib/error.rs @@ -3,6 +3,7 @@ pub enum SchemaError { NotImplemented, InvalidClass, // FIXME: Is there a way to say what we are missing on error? + // Yes, add a string on the enum. MissingMustAttribute, InvalidAttribute, InvalidAttributeSyntax, @@ -14,4 +15,5 @@ pub enum OperationError { EmptyRequest, Backend, SchemaViolation, + Plugin, } diff --git a/src/lib/event.rs b/src/lib/event.rs index 3f79e867b..36415766d 100644 --- a/src/lib/event.rs +++ b/src/lib/event.rs @@ -51,6 +51,7 @@ impl SearchResult { #[derive(Debug)] pub struct SearchEvent { + pub internal: bool, pub filter: Filter, class: (), // String } @@ -62,20 +63,24 @@ impl Message for SearchEvent { impl SearchEvent { pub fn from_request(request: SearchRequest) -> Self { SearchEvent { + internal: false, filter: request.filter, class: (), } } - // We need event -> some kind of json event string for logging - // Could we turn the event from json back to an event for testing later? } +// Represents the decoded entries from the protocol -> internal entry representation +// including information about the identity performing the request, and if the +// request is internal or not. #[derive(Debug)] pub struct CreateEvent { // This may still actually change to handle the *raw* nature of the // input that we plan to parse. pub entries: Vec, - // It could be better to box this later ... + /// Is the CreateEvent from an internal or external source? + /// This may affect which plugins are run ... + pub internal: bool, } impl Message for CreateEvent { @@ -89,11 +94,16 @@ impl CreateEvent { // From ProtoEntry -> Entry // What is the correct consuming iterator here? Can we // even do that? + internal: false, entries: request.entries.iter().map(|e| Entry::from(e)).collect(), } } + // Is this an internal only function? pub fn from_vec(entries: Vec) -> Self { - CreateEvent { entries: entries } + CreateEvent { + internal: false, + entries: entries, + } } } diff --git a/src/lib/identity.rs b/src/lib/identity.rs new file mode 100644 index 000000000..b0fd7427b --- /dev/null +++ b/src/lib/identity.rs @@ -0,0 +1,2 @@ +// Contains a structure representing the current authenticated +// identity (or anonymous, or admin, both of which are in mem). diff --git a/src/lib/lib.rs b/src/lib/lib.rs index bb44532a4..8d16b128a 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -31,9 +31,10 @@ mod audit; mod be; mod entry; mod event; +mod identity; +mod plugins; mod schema; mod server; -mod plugins; pub mod config; pub mod core; diff --git a/src/lib/plugins/mod.rs b/src/lib/plugins/mod.rs index 541f05966..6161e6629 100644 --- a/src/lib/plugins/mod.rs +++ b/src/lib/plugins/mod.rs @@ -1,3 +1,49 @@ +use audit::AuditScope; +use be::Backend; +use entry::Entry; +use event::CreateEvent; +use schema::Schema; +use error::OperationError; + +trait Plugin { + fn pre_create( + be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema, + ) -> Result<(), OperationError> { + Ok(()) + } + + fn post_create() -> Result<(), OperationError> { + Ok(()) + } + + fn pre_modify() -> Result<(), OperationError> { + Ok(()) + } + + fn post_modify() -> Result<(), OperationError> { + Ok(()) + } + + fn pre_delete() -> Result<(), OperationError> { + Ok(()) + } + + fn post_delete() -> Result<(), OperationError> { + Ok(()) + } + + fn pre_search() -> Result<(), OperationError> { + Ok(()) + } + + fn post_search() -> Result<(), OperationError> { + Ok(()) + } +} mod uuid; @@ -5,4 +51,3 @@ mod uuid; // How do we deal with plugin activation? Config? // What do plugins default to? - diff --git a/src/lib/plugins/uuid.rs b/src/lib/plugins/uuid.rs index e69de29bb..97da29511 100644 --- a/src/lib/plugins/uuid.rs +++ b/src/lib/plugins/uuid.rs @@ -0,0 +1,198 @@ +use plugins::Plugin; +use uuid::Uuid; + +use audit::AuditScope; +use be::Backend; +use entry::Entry; +use event::CreateEvent; +use schema::Schema; +use error::OperationError; + +struct UUID {} + +impl Plugin for UUID { + // Need to be given the backend(for testing ease) + // audit + // the mut set of entries to create + // the create event itself (immutable, for checking originals) + // contains who is creating them + // the schema of the running instance + + fn pre_create( + be: &mut Backend, + au: &mut AuditScope, + cand: &mut Vec, + ce: &CreateEvent, + schema: &Schema, + ) -> Result<(), OperationError> { + // For each candidate + for entry in cand.iter_mut() { + let name_uuid = String::from("uuid"); + + // 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(); + // This could actually fail, so we probably need to handle + // this better .... + match Uuid::parse_str(v.as_str()) { + Ok(up) => up, + Err(_) => { + return Err( + OperationError::Plugin + ) + } + } + } + None => Uuid::new_v4() + }; + + // Make it a string, so we can filter. + println!("uuid: {}", c_uuid); + + + // 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); + let ava_uuid: Vec = vec![str_uuid]; + entry.set_avas(name_uuid, ava_uuid); + } + // done! + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::super::Plugin; + use super::UUID; + + use audit::AuditScope; + use be::Backend; + use entry::Entry; + use event::CreateEvent; + use schema::Schema; + + macro_rules! run_pre_create_test { + ( + $preload_entries:ident, + $create_entries:ident, + $ident:ident, + $internal:ident, + $test_fn:expr + ) => {{ + let mut au = AuditScope::new("run_pre_create_test"); + audit_segment!(au, || { + // Create an in memory BE + let mut be = Backend::new(&mut au, ""); + + // TODO: Preload entries here! + + let ce = CreateEvent::from_vec($create_entries.clone()); + let mut schema = Schema::new(); + schema.bootstrap_core(); + + let mut au_test = AuditScope::new("pre_create_test"); + audit_segment!(au_test, || $test_fn( + &mut be, + &mut au_test, + &mut $create_entries, + &ce, + &schema, + )); + + au.append_scope(au_test); + }); + // Dump the raw audit. Perhaps we should serialise this pretty? + println!("{:?}", au); + }}; + } + + // 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!( + 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()); + // Nothing should have changed. + assert!(cand.len() == 0); + } + ); + } + + // 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( + r#"{ + "attrs": { + "class": ["person"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"] + } + }"#, + ) + .unwrap(); + + let mut create = 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()); + // Assert that the entry contains the attr "uuid" now. + let ue = cand.first().unwrap(); + assert!(ue.attribute_pres("uuid")); + } + ); + } + + // check unparseable uuid + // check entry where uuid is empty list + + // check create where provided uuid is valid. It should be unchanged. + + // check create where uuid already exists. + + // check create where uuid is a well-known + // WARNING: This actually requires me to implement backend migrations and + // creation of default objects in the DB on new() if they don't exist, and + // to potentially support migrations of said objects. +} diff --git a/src/lib/schema.rs b/src/lib/schema.rs index c49f802be..0196f60af 100644 --- a/src/lib/schema.rs +++ b/src/lib/schema.rs @@ -749,7 +749,7 @@ impl Schema { None => avas.clone(), }; // now push those to the new entry. - entry_new.add_avas(attr_name_normal, avas_normal); + entry_new.set_avas(attr_name_normal, avas_normal); } // Done! entry_new diff --git a/src/lib/server.rs b/src/lib/server.rs index 9f2531d7c..43bf1b342 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -3,14 +3,13 @@ use actix::prelude::*; use audit::AuditScope; use be::{Backend, BackendError}; -use plugins; use entry::Entry; use error::OperationError; use event::{CreateEvent, OpResult, SearchEvent, SearchResult}; use log::EventLog; +use plugins; use schema::Schema; - pub fn start(log: actix::Addr, path: &str, threads: usize) -> actix::Addr { let mut audit = AuditScope::new("server_start"); audit_segment!(audit, || { @@ -83,13 +82,22 @@ impl QueryServer { res } - // What should this take? - // This should probably take raw encoded entries? Or sohuld they - // be handled by fe? pub fn create(&mut self, au: &mut AuditScope, ce: &CreateEvent) -> Result<(), OperationError> { + // The create event is a raw, read only representation of the request + // that was made to us, including information about the identity + // performing the request. + + // Log the request + + // TODO: Do we need limits on number of creates, or do we constraint + // based on request size in the frontend? + + // Copy the entries to a writeable form. + let mut candidates: Vec<_> = ce.entries.iter().collect(); + // Start a txn - // run any pre plugins + // run any pre plugins, giving them the list of mutable candidates. // Run any pre checks // FIXME: Normalise all entries incoming @@ -117,10 +125,13 @@ impl QueryServer { BackendError::EmptyRequest => OperationError::EmptyRequest, _ => OperationError::Backend, }); - au.append_scope(audit_be); // Run any post plugins + // Commit/Abort the txn + + // We are complete, finalise logging and return + au.append_scope(audit_be); res } }