mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
Schema aware create now works!
This commit is contained in:
parent
56264b5b7b
commit
dddd04898c
|
@ -274,9 +274,7 @@ mod tests {
|
||||||
assert_eq!(empty_result, Err(BackendError::EmptyRequest));
|
assert_eq!(empty_result, Err(BackendError::EmptyRequest));
|
||||||
|
|
||||||
let mut e: Entry = Entry::new();
|
let mut e: Entry = Entry::new();
|
||||||
e.add_ava(String::from("userid"), String::from("william"))
|
e.add_ava(String::from("userid"), String::from("william"));
|
||||||
.unwrap();
|
|
||||||
assert!(e.validate());
|
|
||||||
|
|
||||||
let single_result = be.create(audit, &vec![e]);
|
let single_result = be.create(audit, &vec![e]);
|
||||||
|
|
||||||
|
|
86
src/entry.rs
86
src/entry.rs
|
@ -88,27 +88,48 @@ impl Entry {
|
||||||
|
|
||||||
// This should always work? It's only on validate that we'll build
|
// This should always work? It's only on validate that we'll build
|
||||||
// a list of syntax violations ...
|
// a list of syntax violations ...
|
||||||
pub fn add_ava(&mut self, attr: String, value: String) -> Result<(), ()> {
|
// If this already exists, we silently drop the event? Is that an
|
||||||
|
// acceptable interface?
|
||||||
|
// Should value here actually be a &str?
|
||||||
|
pub fn add_ava(&mut self, attr: String, value: String) {
|
||||||
// get_mut to access value
|
// get_mut to access value
|
||||||
|
// How do we make this turn into an ok / err?
|
||||||
self.attrs
|
self.attrs
|
||||||
.entry(attr)
|
.entry(attr)
|
||||||
.and_modify(|v| v.push(value.clone()))
|
.and_modify(|v| {
|
||||||
|
// Here we need to actually do a check/binary search ...
|
||||||
|
v.binary_search(&value).map_err(|idx| {
|
||||||
|
// This cloning is to fix a borrow issue ...
|
||||||
|
v.insert(idx, value.clone())
|
||||||
|
});
|
||||||
|
})
|
||||||
.or_insert(vec![value]);
|
.or_insert(vec![value]);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ava(&self, attr: &String) -> Option<&Vec<String>> {
|
pub fn get_ava(&self, attr: &String) -> Option<&Vec<String>> {
|
||||||
self.attrs.get(attr)
|
self.attrs.get(attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> bool {
|
pub fn attribute_pres(&self, attr: &str) -> bool {
|
||||||
// We need access to the current system schema here now ...
|
// FIXME: Do we need to normalise attr name?
|
||||||
true
|
self.attrs.contains_key(attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pres(&self, attr: &str) -> bool {
|
pub fn attribute_equality(&self, attr: &str, value: &str) -> bool {
|
||||||
self.attrs.contains_key(attr)
|
// Do a schema aware equality?
|
||||||
|
// Either we get schema passed in.
|
||||||
|
// OR we assume based on schema normalisation on the way in
|
||||||
|
// that the equality here of the raw values MUST be correct.
|
||||||
|
// If we do this, we likely need a DB normalise function ...
|
||||||
|
// The other issue is we have to normalise what's in the filter
|
||||||
|
// but that could be done *before* we get here?
|
||||||
|
|
||||||
|
// FIXME: Make this binary_search
|
||||||
|
|
||||||
|
self.attrs.get(attr).map_or(false, |v| {
|
||||||
|
v.iter()
|
||||||
|
.fold(false, |acc, av| if acc { acc } else { value == av })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn classes(&self) -> EntryClasses {
|
pub fn classes(&self) -> EntryClasses {
|
||||||
|
@ -229,42 +250,55 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_user_basic() {
|
fn test_user_basic() {
|
||||||
let u: User = User::new("william", "William Brown");
|
let u: User = User::new("william", "William Brown");
|
||||||
|
|
||||||
println!("u: {:?}", u);
|
|
||||||
|
|
||||||
let d = serde_json::to_string_pretty(&u).unwrap();
|
let d = serde_json::to_string_pretty(&u).unwrap();
|
||||||
|
|
||||||
println!("d: {}", d.as_str());
|
|
||||||
|
|
||||||
let u2: User = serde_json::from_str(d.as_str()).unwrap();
|
let u2: User = serde_json::from_str(d.as_str()).unwrap();
|
||||||
|
|
||||||
println!("u2: {:?}", u2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_entry_basic() {
|
fn test_entry_basic() {
|
||||||
let mut e: Entry = Entry::new();
|
let mut e: Entry = Entry::new();
|
||||||
|
|
||||||
e.add_ava(String::from("userid"), String::from("william"))
|
e.add_ava(String::from("userid"), String::from("william"));
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(e.validate());
|
|
||||||
|
|
||||||
let d = serde_json::to_string_pretty(&e).unwrap();
|
let d = serde_json::to_string_pretty(&e).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
println!("d: {}", d.as_str());
|
#[test]
|
||||||
|
fn test_entry_dup_value() {
|
||||||
|
// Schema doesn't matter here because we are duplicating a value
|
||||||
|
// it should fail!
|
||||||
|
|
||||||
|
// We still probably need schema here anyway to validate what we
|
||||||
|
// are adding ... Or do we validate after the changes are made in
|
||||||
|
// total?
|
||||||
|
let mut e: Entry = Entry::new();
|
||||||
|
e.add_ava(String::from("userid"), String::from("william"));
|
||||||
|
e.add_ava(String::from("userid"), String::from("william"));
|
||||||
|
|
||||||
|
let values = e.get_ava(&String::from("userid")).unwrap();
|
||||||
|
// Should only be one value!
|
||||||
|
assert_eq!(values.len(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_entry_pres() {
|
fn test_entry_pres() {
|
||||||
let mut e: Entry = Entry::new();
|
let mut e: Entry = Entry::new();
|
||||||
|
|
||||||
e.add_ava(String::from("userid"), String::from("william"))
|
e.add_ava(String::from("userid"), String::from("william"));
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(e.validate());
|
assert!(e.attribute_pres("userid"));
|
||||||
|
assert!(!e.attribute_pres("name"));
|
||||||
|
}
|
||||||
|
|
||||||
assert!(e.pres("userid"));
|
#[test]
|
||||||
assert!(!e.pres("name"));
|
fn test_entry_equality() {
|
||||||
|
let mut e: Entry = Entry::new();
|
||||||
|
|
||||||
|
e.add_ava(String::from("userid"), String::from("william"));
|
||||||
|
|
||||||
|
assert!(e.attribute_equality("userid", "william"));
|
||||||
|
assert!(!e.attribute_equality("userid", "test"));
|
||||||
|
assert!(!e.attribute_equality("nonexist", "william"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,5 @@ pub enum SchemaError {
|
||||||
MISSING_MUST_ATTRIBUTE,
|
MISSING_MUST_ATTRIBUTE,
|
||||||
INVALID_ATTRIBUTE,
|
INVALID_ATTRIBUTE,
|
||||||
INVALID_ATTRIBUTE_SYNTAX,
|
INVALID_ATTRIBUTE_SYNTAX,
|
||||||
|
EMPTY_FILTER,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// entry to assert it matches.
|
// entry to assert it matches.
|
||||||
|
|
||||||
use super::entry::Entry;
|
use super::entry::Entry;
|
||||||
|
use super::schema::Schema;
|
||||||
use std::cmp::{Ordering, PartialOrd};
|
use std::cmp::{Ordering, PartialOrd};
|
||||||
|
|
||||||
// Perhaps make these json serialisable. Certainly would make parsing
|
// Perhaps make these json serialisable. Certainly would make parsing
|
||||||
|
@ -20,7 +21,9 @@ pub enum Filter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
fn optimise(mut self) -> Self {
|
// Does this need mut self? Aren't we returning
|
||||||
|
// a new copied filter?
|
||||||
|
fn optimise(&self) -> Self {
|
||||||
// Apply optimisations to the filter
|
// Apply optimisations to the filter
|
||||||
// An easy way would be imple partialOrd
|
// An easy way would be imple partialOrd
|
||||||
// then do sort on the or/and/not
|
// then do sort on the or/and/not
|
||||||
|
@ -33,12 +36,7 @@ impl Filter {
|
||||||
// If an or/not/and condition has no items, remove it
|
// If an or/not/and condition has no items, remove it
|
||||||
//
|
//
|
||||||
// If its the root item?
|
// If its the root item?
|
||||||
self
|
self.clone()
|
||||||
}
|
|
||||||
|
|
||||||
// In the future this will probably be used with schema ...
|
|
||||||
fn validate(&self) -> Result<(), ()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is probably not safe, so it's for internal test cases
|
// This is probably not safe, so it's for internal test cases
|
||||||
|
@ -62,7 +60,7 @@ impl Filter {
|
||||||
Filter::Sub(_, _) => false,
|
Filter::Sub(_, _) => false,
|
||||||
Filter::Pres(attr) => {
|
Filter::Pres(attr) => {
|
||||||
// Given attr, is is present in the entry?
|
// Given attr, is is present in the entry?
|
||||||
e.pres(attr.as_str())
|
e.attribute_pres(attr.as_str())
|
||||||
}
|
}
|
||||||
Filter::Or(_) => false,
|
Filter::Or(_) => false,
|
||||||
Filter::And(_) => false,
|
Filter::And(_) => false,
|
||||||
|
@ -130,6 +128,7 @@ impl PartialOrd for Filter {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::super::schema::Schema;
|
||||||
use super::Filter;
|
use super::Filter;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::cmp::{Ordering, PartialOrd};
|
use std::cmp::{Ordering, PartialOrd};
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
|
|
||||||
#![feature(try_from)]
|
#![feature(try_from)]
|
||||||
|
|
||||||
|
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
789
src/schema.rs
789
src/schema.rs
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@ use be::Backend;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use event::{CreateEvent, EventResult, SearchEvent};
|
use event::{CreateEvent, EventResult, SearchEvent};
|
||||||
use log::EventLog;
|
use log::EventLog;
|
||||||
|
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 = AuditEvent::new();
|
let mut audit = AuditEvent::new();
|
||||||
|
@ -12,12 +13,16 @@ pub fn start(log: actix::Addr<EventLog>, path: &str, threads: usize) -> actix::A
|
||||||
// Create the BE connection
|
// Create the BE connection
|
||||||
// probably need a config type soon ....
|
// probably need a config type soon ....
|
||||||
let be = Backend::new(&mut audit, path);
|
let be = Backend::new(&mut audit, path);
|
||||||
|
let mut schema = Schema::new();
|
||||||
|
schema.bootstrap_core();
|
||||||
// now we clone it out in the startup I think
|
// now we clone it out in the startup I think
|
||||||
// Should the be need a log clone ref? or pass it around?
|
// Should the be need a log clone ref? or pass it around?
|
||||||
// it probably needs it ...
|
// it probably needs it ...
|
||||||
audit.end_event("server_new");
|
audit.end_event("server_new");
|
||||||
log.do_send(audit);
|
log.do_send(audit);
|
||||||
SyncArbiter::start(threads, move || QueryServer::new(log.clone(), be.clone()))
|
SyncArbiter::start(threads, move || {
|
||||||
|
QueryServer::new(log.clone(), be.clone(), schema.clone())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the core of the server. It implements all
|
// This is the core of the server. It implements all
|
||||||
|
@ -35,12 +40,17 @@ pub struct QueryServer {
|
||||||
// I think the BE is build, configured and cloned? Maybe Backend
|
// I think the BE is build, configured and cloned? Maybe Backend
|
||||||
// is a wrapper type to Arc<BackendInner> or something.
|
// is a wrapper type to Arc<BackendInner> or something.
|
||||||
be: Backend,
|
be: Backend,
|
||||||
|
schema: Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryServer {
|
impl QueryServer {
|
||||||
pub fn new(log: actix::Addr<EventLog>, be: Backend) -> Self {
|
pub fn new(log: actix::Addr<EventLog>, be: Backend, schema: Schema) -> Self {
|
||||||
log_event!(log, "Starting query worker ...");
|
log_event!(log, "Starting query worker ...");
|
||||||
QueryServer { log: log, be: be }
|
QueryServer {
|
||||||
|
log: log,
|
||||||
|
be: be,
|
||||||
|
schema: schema,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually conduct a search request
|
// Actually conduct a search request
|
||||||
|
@ -59,6 +69,18 @@ impl QueryServer {
|
||||||
pub fn create(&mut self, au: &mut AuditEvent, ce: &CreateEvent) -> Result<(), ()> {
|
pub fn create(&mut self, au: &mut AuditEvent, ce: &CreateEvent) -> Result<(), ()> {
|
||||||
// Start a txn
|
// Start a txn
|
||||||
// Run any pre checks
|
// Run any pre checks
|
||||||
|
|
||||||
|
let r = ce.entries.iter().fold(Ok(()), |acc, e| {
|
||||||
|
if acc.is_ok() {
|
||||||
|
self.schema.validate_entry(e).map_err(|_| ())
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if r.is_err() {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
// We may change from ce.entries later to something else?
|
// We may change from ce.entries later to something else?
|
||||||
match self.be.create(au, &ce.entries) {
|
match self.be.create(au, &ce.entries) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@ -146,6 +168,7 @@ mod tests {
|
||||||
use super::super::event::{CreateEvent, SearchEvent};
|
use super::super::event::{CreateEvent, SearchEvent};
|
||||||
use super::super::filter::Filter;
|
use super::super::filter::Filter;
|
||||||
use super::super::log;
|
use super::super::log;
|
||||||
|
use super::super::schema::Schema;
|
||||||
use super::super::server::QueryServer;
|
use super::super::server::QueryServer;
|
||||||
|
|
||||||
macro_rules! run_test {
|
macro_rules! run_test {
|
||||||
|
@ -155,7 +178,9 @@ mod tests {
|
||||||
let test_log = log::start();
|
let test_log = log::start();
|
||||||
|
|
||||||
let be = Backend::new(&mut audit, "");
|
let be = Backend::new(&mut audit, "");
|
||||||
let test_server = QueryServer::new(test_log.clone(), be);
|
let mut schema = Schema::new();
|
||||||
|
schema.bootstrap_core();
|
||||||
|
let test_server = QueryServer::new(test_log.clone(), be, schema);
|
||||||
|
|
||||||
// Could wrap another future here for the future::ok bit...
|
// Could wrap another future here for the future::ok bit...
|
||||||
let fut = $test_fn(test_log.clone(), test_server, &mut audit);
|
let fut = $test_fn(test_log.clone(), test_server, &mut audit);
|
||||||
|
@ -174,13 +199,21 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_be_create_user() {
|
fn test_be_create_user() {
|
||||||
run_test!(|_log, mut server: QueryServer, audit: &mut AuditEvent| {
|
run_test!(|_log, mut server: QueryServer, audit: &mut AuditEvent| {
|
||||||
let filt = Filter::Pres(String::from("userid"));
|
let filt = Filter::Pres(String::from("name"));
|
||||||
|
|
||||||
let se1 = SearchEvent::new(filt.clone());
|
let se1 = SearchEvent::new(filt.clone());
|
||||||
let se2 = SearchEvent::new(filt);
|
let se2 = SearchEvent::new(filt);
|
||||||
|
|
||||||
let mut e: Entry = Entry::new();
|
let e: Entry = serde_json::from_str(
|
||||||
e.add_ava(String::from("userid"), String::from("william"))
|
r#"{
|
||||||
|
"attrs": {
|
||||||
|
"class": ["person"],
|
||||||
|
"name": ["testperson"],
|
||||||
|
"description": ["testperson"],
|
||||||
|
"displayname": ["testperson"]
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected = vec![e];
|
let expected = vec![e];
|
||||||
|
@ -194,6 +227,7 @@ mod tests {
|
||||||
assert!(cr.is_ok());
|
assert!(cr.is_ok());
|
||||||
|
|
||||||
let r2 = server.search(audit, &se2).unwrap();
|
let r2 = server.search(audit, &se2).unwrap();
|
||||||
|
println!("--> {:?}", r2);
|
||||||
assert!(r2.len() == 1);
|
assert!(r2.len() == 1);
|
||||||
|
|
||||||
assert_eq!(r2, expected);
|
assert_eq!(r2, expected);
|
||||||
|
|
Loading…
Reference in a new issue