Working uuid test case for simple create case

This commit is contained in:
William Brown 2018-12-29 12:29:10 +10:00
parent 6bd5b8856b
commit e26081dad5
11 changed files with 297 additions and 30 deletions

View file

@ -1,4 +1,3 @@
extern crate rsidm; extern crate rsidm;
use rsidm::proto_v1; use rsidm::proto_v1;

View file

@ -1,9 +1,9 @@
// use actix::SystemRunner; // use actix::SystemRunner;
use actix_web::middleware::session::{self, RequestSession};
use actix_web::{ use actix_web::{
error, http, middleware, App, AsyncResponder, Error, FutureResponse, HttpMessage, HttpRequest, 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 bytes::BytesMut;
use futures::{future, Future, Stream}; use futures::{future, Future, Stream};
@ -11,8 +11,7 @@ use futures::{future, Future, Stream};
use super::config::Configuration; use super::config::Configuration;
// SearchResult // SearchResult
use super::event::{CreateEvent, SearchEvent, use super::event::{CreateEvent, SearchEvent};
};
use super::filter::Filter; use super::filter::Filter;
use super::log; use super::log;
use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse}; use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse};
@ -151,7 +150,6 @@ fn whoami(req: &HttpRequest<AppState>) -> Result<&'static str> {
Ok("welcome!") Ok("welcome!")
} }
pub fn create_server_core(config: Configuration) { pub fn create_server_core(config: Configuration) {
// Configure the middleware logger // Configure the middleware logger
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
@ -187,7 +185,7 @@ pub fn create_server_core(config: Configuration) {
.http_only(true) .http_only(true)
.name("rsidm-session") .name("rsidm-session")
// This forces https only // This forces https only
.secure(false) .secure(false),
)) ))
.resource("/", |r| r.f(index)) .resource("/", |r| r.f(index))
// curl --header ...? // curl --header ...?

View file

@ -130,7 +130,8 @@ impl Entry {
} }
// FIXME: Should this collect from iter instead? // FIXME: Should this collect from iter instead?
pub fn add_avas(&mut self, attr: String, values: Vec<String>) { /// Overwrite the existing avas.
pub fn set_avas(&mut self, attr: String, values: Vec<String>) {
// Overwrite the existing value // Overwrite the existing value
let _ = self.attrs.insert(attr, values); let _ = self.attrs.insert(attr, values);
} }

View file

@ -3,6 +3,7 @@ pub enum SchemaError {
NotImplemented, NotImplemented,
InvalidClass, InvalidClass,
// FIXME: Is there a way to say what we are missing on error? // FIXME: Is there a way to say what we are missing on error?
// Yes, add a string on the enum.
MissingMustAttribute, MissingMustAttribute,
InvalidAttribute, InvalidAttribute,
InvalidAttributeSyntax, InvalidAttributeSyntax,
@ -14,4 +15,5 @@ pub enum OperationError {
EmptyRequest, EmptyRequest,
Backend, Backend,
SchemaViolation, SchemaViolation,
Plugin,
} }

View file

@ -51,6 +51,7 @@ impl SearchResult {
#[derive(Debug)] #[derive(Debug)]
pub struct SearchEvent { pub struct SearchEvent {
pub internal: bool,
pub filter: Filter, pub filter: Filter,
class: (), // String class: (), // String
} }
@ -62,20 +63,24 @@ impl Message for SearchEvent {
impl SearchEvent { impl SearchEvent {
pub fn from_request(request: SearchRequest) -> Self { pub fn from_request(request: SearchRequest) -> Self {
SearchEvent { SearchEvent {
internal: false,
filter: request.filter, filter: request.filter,
class: (), 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)] #[derive(Debug)]
pub struct CreateEvent { pub struct CreateEvent {
// This may still actually change to handle the *raw* nature of the // This may still actually change to handle the *raw* nature of the
// input that we plan to parse. // input that we plan to parse.
pub entries: Vec<Entry>, pub entries: Vec<Entry>,
// 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 { impl Message for CreateEvent {
@ -89,11 +94,16 @@ impl CreateEvent {
// From ProtoEntry -> Entry // From ProtoEntry -> Entry
// What is the correct consuming iterator here? Can we // What is the correct consuming iterator here? Can we
// even do that? // even do that?
internal: false,
entries: request.entries.iter().map(|e| Entry::from(e)).collect(), entries: request.entries.iter().map(|e| Entry::from(e)).collect(),
} }
} }
// Is this an internal only function?
pub fn from_vec(entries: Vec<Entry>) -> Self { pub fn from_vec(entries: Vec<Entry>) -> Self {
CreateEvent { entries: entries } CreateEvent {
internal: false,
entries: entries,
}
} }
} }

2
src/lib/identity.rs Normal file
View file

@ -0,0 +1,2 @@
// Contains a structure representing the current authenticated
// identity (or anonymous, or admin, both of which are in mem).

View file

@ -31,9 +31,10 @@ mod audit;
mod be; mod be;
mod entry; mod entry;
mod event; mod event;
mod identity;
mod plugins;
mod schema; mod schema;
mod server; mod server;
mod plugins;
pub mod config; pub mod config;
pub mod core; pub mod core;

View file

@ -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<Entry>,
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; mod uuid;
@ -5,4 +51,3 @@ mod uuid;
// How do we deal with plugin activation? Config? // How do we deal with plugin activation? Config?
// What do plugins default to? // What do plugins default to?

View file

@ -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<Entry>,
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<String> = 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<Entry> = Vec::new();
let mut create: Vec<Entry> = Vec::new();
run_pre_create_test!(
preload,
create,
false,
false,
|be: &mut Backend,
au: &mut AuditScope,
cand: &mut Vec<Entry>,
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<Entry> = 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<Entry>,
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.
}

View file

@ -749,7 +749,7 @@ impl Schema {
None => avas.clone(), None => avas.clone(),
}; };
// now push those to the new entry. // 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! // Done!
entry_new entry_new

View file

@ -3,14 +3,13 @@ use actix::prelude::*;
use audit::AuditScope; use audit::AuditScope;
use be::{Backend, BackendError}; use be::{Backend, BackendError};
use plugins;
use entry::Entry; use entry::Entry;
use error::OperationError; use error::OperationError;
use event::{CreateEvent, OpResult, SearchEvent, SearchResult}; use event::{CreateEvent, OpResult, SearchEvent, SearchResult};
use log::EventLog; use log::EventLog;
use plugins;
use schema::Schema; use schema::Schema;
pub fn start(log: actix::Addr<EventLog>, path: &str, threads: usize) -> actix::Addr<QueryServer> { pub fn start(log: actix::Addr<EventLog>, path: &str, threads: usize) -> actix::Addr<QueryServer> {
let mut audit = AuditScope::new("server_start"); let mut audit = AuditScope::new("server_start");
audit_segment!(audit, || { audit_segment!(audit, || {
@ -83,13 +82,22 @@ impl QueryServer {
res 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> { 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 // Start a txn
// run any pre plugins // run any pre plugins, giving them the list of mutable candidates.
// Run any pre checks // Run any pre checks
// FIXME: Normalise all entries incoming // FIXME: Normalise all entries incoming
@ -117,10 +125,13 @@ impl QueryServer {
BackendError::EmptyRequest => OperationError::EmptyRequest, BackendError::EmptyRequest => OperationError::EmptyRequest,
_ => OperationError::Backend, _ => OperationError::Backend,
}); });
au.append_scope(audit_be);
// Run any post plugins // Run any post plugins
// Commit/Abort the txn // Commit/Abort the txn
// We are complete, finalise logging and return
au.append_scope(audit_be);
res res
} }
} }