mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
Working modify internal and migrations
This commit is contained in:
parent
764695db4a
commit
3b3f5dc6d5
|
@ -156,7 +156,7 @@ impl Drop for BackendTransaction {
|
|||
impl BackendTransaction {
|
||||
pub fn new(conn: r2d2::PooledConnection<SqliteConnectionManager>) -> Self {
|
||||
// Start the transaction
|
||||
println!("Starting txn ...");
|
||||
println!("Starting RO txn ...");
|
||||
// TODO: Way to flag that this will be a read?
|
||||
conn.execute("BEGIN TRANSACTION", NO_PARAMS).unwrap();
|
||||
BackendTransaction {
|
||||
|
@ -196,7 +196,7 @@ impl BackendReadTransaction for BackendWriteTransaction {
|
|||
impl BackendWriteTransaction {
|
||||
pub fn new(conn: r2d2::PooledConnection<SqliteConnectionManager>) -> Self {
|
||||
// Start the transaction
|
||||
println!("Starting txn ...");
|
||||
println!("Starting WR txn ...");
|
||||
// TODO: Way to flag that this will be a write?
|
||||
conn.execute("BEGIN TRANSACTION", NO_PARAMS).unwrap();
|
||||
BackendWriteTransaction {
|
||||
|
@ -273,8 +273,51 @@ impl BackendWriteTransaction {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn modify() -> Result<(), BackendError> {
|
||||
unimplemented!()
|
||||
pub fn modify(&self, au: &mut AuditScope, entries: &Vec<Entry>) -> Result<(), BackendError> {
|
||||
if entries.is_empty() {
|
||||
// TODO: Better error
|
||||
return Err(BackendError::EmptyRequest);
|
||||
}
|
||||
|
||||
// Assert the Id's exist on the entry, and serialise them.
|
||||
let ser_entries: Vec<IdEntry> = entries
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
match e.id {
|
||||
Some(id) => {
|
||||
Some(IdEntry {
|
||||
id: id,
|
||||
// TODO: Should we do better than unwrap?
|
||||
data: serde_json::to_string(&e).unwrap(),
|
||||
})
|
||||
}
|
||||
None => None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
audit_log!(au, "serialising: {:?}", ser_entries);
|
||||
|
||||
// Simple: If the list of id's is not the same as the input list, we are missing id's
|
||||
// TODO: This check won't be needed once I rebuild the entry state types.
|
||||
if entries.len() != ser_entries.len() {
|
||||
return Err(BackendError::EntryMissingId);
|
||||
}
|
||||
|
||||
// Now, given the list of id's, update them
|
||||
{
|
||||
// TODO: ACTUALLY HANDLE THIS ERROR WILLIAM YOU LAZY SHIT.
|
||||
let mut stmt = self.conn.prepare("UPDATE id2entry SET data = :data WHERE id = :id").unwrap();
|
||||
|
||||
ser_entries.iter().for_each(|ser_ent| {
|
||||
stmt.execute_named(&[
|
||||
(":id", &ser_ent.id),
|
||||
(":data", &ser_ent.data),
|
||||
]).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(&self, au: &mut AuditScope, entries: &Vec<Entry>) -> Result<(), BackendError> {
|
||||
|
@ -282,7 +325,6 @@ impl BackendWriteTransaction {
|
|||
|
||||
if entries.is_empty() {
|
||||
// TODO: Better error
|
||||
// End the timer
|
||||
return Err(BackendError::EmptyRequest);
|
||||
}
|
||||
|
||||
|
@ -294,12 +336,11 @@ impl BackendWriteTransaction {
|
|||
.collect();
|
||||
|
||||
// Simple: If the list of id's is not the same as the input list, we are missing id's
|
||||
// TODO: This check won't be needed once I rebuild the entry state types.
|
||||
if entries.len() != id_list.len() {
|
||||
return Err(BackendError::EntryMissingId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Now, given the list of id's, delete them.
|
||||
{
|
||||
// SQL doesn't say if the thing "does or does not exist anymore". As a result,
|
||||
|
@ -521,6 +562,19 @@ mod tests {
|
|||
}}
|
||||
}
|
||||
|
||||
macro_rules! entry_attr_pres {
|
||||
($audit:expr, $be:expr, $ent:expr, $attr:expr) => {{
|
||||
let filt = $ent.filter_from_attrs(&vec![String::from("userid")]).unwrap();
|
||||
let entries = $be.search($audit, &filt).unwrap();
|
||||
match entries.first() {
|
||||
Some(ent) => {
|
||||
ent.attribute_pres($attr)
|
||||
}
|
||||
None => false
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_create() {
|
||||
run_test!(|audit: &mut AuditScope, be: &BackendWriteTransaction| {
|
||||
|
@ -553,6 +607,44 @@ mod tests {
|
|||
fn test_simple_modify() {
|
||||
run_test!(|audit: &mut AuditScope, be: &BackendWriteTransaction| {
|
||||
audit_log!(audit, "Simple Modify");
|
||||
// First create some entries (3?)
|
||||
let mut e1: Entry = Entry::new();
|
||||
e1.add_ava(String::from("userid"), String::from("william"));
|
||||
|
||||
let mut e2: Entry = Entry::new();
|
||||
e2.add_ava(String::from("userid"), String::from("alice"));
|
||||
|
||||
assert!(be.create(audit, &vec![e1.clone(), e2.clone()]).is_ok());
|
||||
assert!(entry_exists!(audit, be, e1));
|
||||
assert!(entry_exists!(audit, be, e2));
|
||||
|
||||
// You need to now retrieve the entries back out to get the entry id's
|
||||
let mut results = be.search(audit, &Filter::Pres(String::from("userid"))).unwrap();
|
||||
|
||||
// Get these out to usable entries.
|
||||
let mut r1 = results.remove(0);
|
||||
let mut r2 = results.remove(0);
|
||||
|
||||
// Modify no id (err)
|
||||
assert!(be.modify(audit, &vec![e1.clone()]).is_err());
|
||||
// Modify none
|
||||
assert!(be.modify(audit, &vec![]).is_err());
|
||||
|
||||
// Make some changes to r1, r2.
|
||||
r1.add_ava(String::from("desc"), String::from("modified"));
|
||||
r2.add_ava(String::from("desc"), String::from("modified"));
|
||||
|
||||
// Modify single
|
||||
assert!(be.modify(audit, &vec![r1.clone()]).is_ok());
|
||||
// Assert no other changes
|
||||
assert!(entry_attr_pres!(audit, be, r1, "desc"));
|
||||
assert!(! entry_attr_pres!(audit, be, r2, "desc"));
|
||||
|
||||
// Modify both
|
||||
assert!(be.modify(audit, &vec![r1.clone(), r2.clone()]).is_ok());
|
||||
|
||||
assert!(entry_attr_pres!(audit, be, r1, "desc"));
|
||||
assert!(entry_attr_pres!(audit, be, r2, "desc"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
131
src/lib/entry.rs
131
src/lib/entry.rs
|
@ -4,6 +4,8 @@ use filter::Filter;
|
|||
use std::collections::btree_map::{Iter as BTreeIter, IterMut as BTreeIterMut};
|
||||
use std::collections::BTreeMap;
|
||||
use std::slice::Iter as SliceIter;
|
||||
use modify::{Modify, ModifyList};
|
||||
use schema::SchemaReadTransaction;
|
||||
|
||||
// make a trait entry for everything to adhere to?
|
||||
// * How to get indexs out?
|
||||
|
@ -94,9 +96,23 @@ impl<'a> Iterator for EntryAvasMut<'a> {
|
|||
}
|
||||
|
||||
// This is a BE concept, so move it there!
|
||||
|
||||
// Entry should have a lifecycle of types. THis is Raw (modifiable) and Entry (verified).
|
||||
// This way, we can move between them, but only certain actions are possible on either
|
||||
// This means modifications happen on Raw, but to move to Entry, you schema normalise.
|
||||
// Vice versa, you can for free, move to Raw, but you lose the validation.
|
||||
|
||||
// Because this is type system it's "free" in the end, and means we force validation
|
||||
// at the correct and required points of the entries life.
|
||||
|
||||
// This is specifically important for the commit to the backend, as we only want to
|
||||
// commit validated types.
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Entry {
|
||||
pub id: Option<i64>,
|
||||
// Flag if we have been schema checked or not.
|
||||
// pub schema_validated: bool,
|
||||
attrs: BTreeMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
|
@ -105,6 +121,9 @@ impl Entry {
|
|||
Entry {
|
||||
// This means NEVER COMMITED
|
||||
id: None,
|
||||
// TODO: Make this only on cfg(test/debug_assertions) builds.
|
||||
// ALTERNATE: Convert to a different entry type on validate/normalise?
|
||||
// CONVERT TO A TYPE
|
||||
attrs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +142,7 @@ impl Entry {
|
|||
// Here we need to actually do a check/binary search ...
|
||||
// FIXME: Because map_err is lazy, this won't do anything on release
|
||||
match v.binary_search(&value) {
|
||||
// It already exists, done!
|
||||
Ok(_) => {}
|
||||
Err(idx) => {
|
||||
// This cloning is to fix a borrow issue with the or_insert below.
|
||||
|
@ -134,6 +154,29 @@ impl Entry {
|
|||
.or_insert(vec![value]);
|
||||
}
|
||||
|
||||
pub fn remove_ava(&mut self, attr: &String, value: &String) {
|
||||
self.attrs
|
||||
// TODO: Fix this clone ...
|
||||
.entry(attr.clone())
|
||||
.and_modify(|v| {
|
||||
// Here we need to actually do a check/binary search ...
|
||||
// FIXME: Because map_err is lazy, this won't do anything on release
|
||||
match v.binary_search(&value) {
|
||||
// It exists, rm it.
|
||||
Ok(idx) => {
|
||||
v.remove(idx);
|
||||
}
|
||||
// It does not exist, move on.
|
||||
Err(_) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn purge_ava(&mut self, attr: &String) {
|
||||
self.attrs.remove(attr);
|
||||
}
|
||||
|
||||
// FIXME: Should this collect from iter instead?
|
||||
/// Overwrite the existing avas.
|
||||
pub fn set_avas(&mut self, attr: String, values: Vec<String>) {
|
||||
|
@ -250,6 +293,72 @@ impl Entry {
|
|||
attrs: self.attrs.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_modlist_assert(&self, schema: &SchemaReadTransaction) -> Result<ModifyList, ()>
|
||||
{
|
||||
// Create a modlist from this entry. We make this assuming we want the entry
|
||||
// to have this one as a subset of values. This means if we have single
|
||||
// values, we'll replace, if they are multivalue, we present them.
|
||||
//
|
||||
// We assume the schema validaty of the entry is already checked, and
|
||||
// normalisation performed.
|
||||
let mut mods = ModifyList::new();
|
||||
|
||||
for (k, vs) in self.attrs.iter() {
|
||||
// Get the schema attribute type out.
|
||||
match schema.is_multivalue(k) {
|
||||
Ok(r) => {
|
||||
if !r {
|
||||
// As this is single value, purge then present to maintain this
|
||||
// invariant
|
||||
mods.push_mod(Modify::Purged(k.clone()));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(())
|
||||
}
|
||||
}
|
||||
for v in vs {
|
||||
mods.push_mod(Modify::Present(k.clone(), v.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mods)
|
||||
}
|
||||
|
||||
// Should this be schemaless, relying on checks of the modlist, and the entry validate after?
|
||||
pub fn apply_modlist(&self, modlist: &ModifyList) -> Result<Entry, ()> {
|
||||
// Apply a modlist, generating a new entry that conforms to the changes.
|
||||
// This is effectively clone-and-transform
|
||||
|
||||
// clone the entry
|
||||
let mut ne = self.clone();
|
||||
|
||||
// mutate
|
||||
for modify in modlist.mods.iter() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return it
|
||||
Ok(ne)
|
||||
}
|
||||
|
||||
pub fn clone_no_attrs(&self) -> Entry {
|
||||
Entry {
|
||||
id: self.id,
|
||||
attrs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Entry {
|
||||
|
@ -318,6 +427,7 @@ struct User {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Entry, User};
|
||||
use modify::{Modify, ModifyList};
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
|
@ -349,7 +459,6 @@ mod tests {
|
|||
#[test]
|
||||
fn test_entry_pres() {
|
||||
let mut e: Entry = Entry::new();
|
||||
|
||||
e.add_ava(String::from("userid"), String::from("william"));
|
||||
|
||||
assert!(e.attribute_pres("userid"));
|
||||
|
@ -367,4 +476,24 @@ mod tests {
|
|||
assert!(!e.attribute_equality("nonexist", "william"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry_apply_modlist() {
|
||||
// Test application of changes to an entry.
|
||||
let mut e: Entry = Entry::new();
|
||||
e.add_ava(String::from("userid"), String::from("william"));
|
||||
|
||||
let mods = ModifyList::new_list(vec![
|
||||
Modify::Present(String::from("attr"), String::from("value")),
|
||||
]);
|
||||
|
||||
let ne = e.apply_modlist(&mods).unwrap();
|
||||
|
||||
// Assert the changes are there
|
||||
assert!(ne.attribute_equality("attr", "value"));
|
||||
|
||||
|
||||
// Assert present for multivalue
|
||||
// Assert purge on single/multi/empty value
|
||||
// Assert removed on value that exists and doesn't exist
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ pub enum SchemaError {
|
|||
pub enum OperationError {
|
||||
EmptyRequest,
|
||||
Backend,
|
||||
NoMatchingEntries,
|
||||
SchemaViolation,
|
||||
Plugin,
|
||||
FilterGeneration,
|
||||
|
|
|
@ -4,6 +4,7 @@ use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse};
|
|||
use actix::prelude::*;
|
||||
use entry::Entry;
|
||||
use error::OperationError;
|
||||
use modify::ModifyList;
|
||||
|
||||
// Should the event Result have the log items?
|
||||
// FIXME: Remove seralising here - each type should
|
||||
|
@ -70,6 +71,14 @@ impl SearchEvent {
|
|||
class: (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_internal(filter: Filter) -> Self {
|
||||
SearchEvent {
|
||||
internal: true,
|
||||
filter: filter,
|
||||
class: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Represents the decoded entries from the protocol -> internal entry representation
|
||||
|
@ -156,3 +165,24 @@ impl DeleteEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModifyEvent {
|
||||
pub filter: Filter,
|
||||
pub modlist: ModifyList,
|
||||
pub internal: bool,
|
||||
}
|
||||
|
||||
impl Message for ModifyEvent {
|
||||
type Result = Result<OpResult, OperationError>;
|
||||
}
|
||||
|
||||
impl ModifyEvent {
|
||||
pub fn new_internal(filter: Filter, modlist: ModifyList) -> Self {
|
||||
ModifyEvent {
|
||||
filter: filter,
|
||||
modlist: modlist,
|
||||
internal: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ pub enum Filter {
|
|||
Not(Box<Filter>),
|
||||
}
|
||||
|
||||
// Change this so you have RawFilter and Filter. RawFilter is the "builder", and then
|
||||
// given a "schema" you can emit a Filter. For us internally, we can create Filter
|
||||
// directly still ...
|
||||
|
||||
impl Filter {
|
||||
// Does this need mut self? Aren't we returning
|
||||
// a new copied filter?
|
||||
|
|
|
@ -42,6 +42,7 @@ mod identity;
|
|||
mod plugins;
|
||||
mod schema;
|
||||
mod server;
|
||||
mod modify;
|
||||
|
||||
pub mod config;
|
||||
pub mod core;
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct Base {}
|
|||
|
||||
impl Plugin for Base {
|
||||
fn id() -> &'static str {
|
||||
"Base"
|
||||
"plugin_base"
|
||||
}
|
||||
// Need to be given the backend(for testing ease)
|
||||
// audit
|
||||
|
|
|
@ -9,13 +9,12 @@ use regex::Regex;
|
|||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
use modify::ModifyList;
|
||||
|
||||
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
|
||||
|
||||
// representations of schema that confines object types, classes
|
||||
// and attributes. This ties in deeply with "Entry".
|
||||
// This only defines the types, and how they are represented. For
|
||||
// application and validation of the schema, see "Entry".
|
||||
//
|
||||
// In the future this will parse/read it's schema from the db
|
||||
// but we have to bootstrap with some core types.
|
||||
|
@ -32,6 +31,7 @@ use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
|
|||
// probably just protection from delete and modify, except systemmay/systemmust/index?
|
||||
|
||||
// TODO: Schema types -> Entry conversion
|
||||
// TODO: Entry -> Schema given class. This is for loading from the db.
|
||||
|
||||
// TODO: prefix on all schema types that are system?
|
||||
|
||||
|
@ -300,6 +300,34 @@ pub struct SchemaInner {
|
|||
attributes: HashMap<String, SchemaAttribute>,
|
||||
}
|
||||
|
||||
pub trait SchemaReadTransaction {
|
||||
fn get_inner(&self) -> &SchemaInner;
|
||||
|
||||
fn validate(&self, audit: &mut AuditScope) -> Result<(), ()> {
|
||||
self.get_inner().validate(audit)
|
||||
}
|
||||
|
||||
fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> {
|
||||
self.get_inner().validate_entry(entry)
|
||||
}
|
||||
|
||||
fn validate_filter(&self, filt: &Filter) -> Result<(), SchemaError> {
|
||||
self.get_inner().validate_filter(filt)
|
||||
}
|
||||
|
||||
fn normalise_entry(&self, entry: &Entry) -> Entry {
|
||||
self.get_inner().normalise_entry(entry)
|
||||
}
|
||||
|
||||
fn normalise_modlist(&self, modlist: &ModifyList) -> ModifyList {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_multivalue(&self, attr: &str) -> Result<bool, SchemaError> {
|
||||
self.get_inner().is_multivalue(attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaInner {
|
||||
pub fn new(audit: &mut AuditScope) -> Result<Self, ()> {
|
||||
let mut au = AuditScope::new("schema_new");
|
||||
|
@ -941,13 +969,13 @@ impl SchemaInner {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn normalise_entry(&mut self, entry: &mut Entry) -> Result<(), SchemaError> {
|
||||
// TODO: Restructure this when we change entry lifecycle types.
|
||||
pub fn normalise_entry(&self, entry: &Entry) -> Entry {
|
||||
// We duplicate the entry here, because we can't
|
||||
// modify what we got on the protocol level. It also
|
||||
// lets us extend and change things.
|
||||
|
||||
let mut entry_new: Entry = Entry::new();
|
||||
let mut entry_new: Entry = entry.clone_no_attrs();
|
||||
// Better hope we have the attribute type ...
|
||||
let schema_attr_name = self.attributes.get("name").unwrap();
|
||||
// For each ava
|
||||
|
@ -972,7 +1000,10 @@ impl SchemaInner {
|
|||
// now push those to the new entry.
|
||||
entry_new.set_avas(attr_name_normal, avas_normal);
|
||||
}
|
||||
// Mark it is valid
|
||||
// entry_new.schema_validated = true;
|
||||
// Done!
|
||||
// TODO: Convert the entry type here to a validated type.
|
||||
entry_new
|
||||
}
|
||||
|
||||
|
@ -1035,6 +1066,17 @@ impl SchemaInner {
|
|||
pub fn normalise_filter(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_multivalue(&self, attr_name: &str) -> Result<bool, SchemaError> {
|
||||
match self.attributes.get(attr_name) {
|
||||
Some(a_schema) => {
|
||||
Ok(a_schema.multivalue)
|
||||
}
|
||||
None => {
|
||||
return Err(SchemaError::InvalidAttribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// type Schema = CowCell<SchemaInner>;
|
||||
|
@ -1052,14 +1094,6 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
self.inner.bootstrap_core(audit)
|
||||
}
|
||||
|
||||
pub fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> {
|
||||
self.inner.validate_entry(entry)
|
||||
}
|
||||
|
||||
pub fn normalise_entry(&self, entry: &Entry) -> Entry {
|
||||
self.inner.normalise_entry(entry)
|
||||
}
|
||||
|
||||
// TODO: Schema probably needs to be part of the backend, so that commits are wholly atomic
|
||||
// but in the current design, we need to open be first, then schema, but we have to commit be
|
||||
// first, then schema to ensure that the be content matches our schema. Saying this, if your
|
||||
|
@ -1068,13 +1102,12 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
pub fn commit(self) {
|
||||
self.inner.commit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_filter(&self, filt: &Filter) -> Result<(), SchemaError> {
|
||||
self.inner.validate_filter(filt)
|
||||
}
|
||||
|
||||
pub fn validate(&self, audit: &mut AuditScope) -> Result<(), ()> {
|
||||
self.inner.validate(audit)
|
||||
impl<'a> SchemaReadTransaction for SchemaWriteTransaction<'a> {
|
||||
fn get_inner(&self) -> &SchemaInner {
|
||||
// Does this deref the CowCell for us?
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1082,17 +1115,10 @@ pub struct SchemaTransaction {
|
|||
inner: CowCellReadTxn<SchemaInner>,
|
||||
}
|
||||
|
||||
impl SchemaTransaction {
|
||||
pub fn validate(&self, audit: &mut AuditScope) -> Result<(), ()> {
|
||||
self.inner.validate(audit)
|
||||
}
|
||||
|
||||
pub fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> {
|
||||
self.inner.validate_entry(entry)
|
||||
}
|
||||
|
||||
pub fn validate_filter(&self, filt: &Filter) -> Result<(), SchemaError> {
|
||||
self.inner.validate_filter(filt)
|
||||
impl SchemaReadTransaction for SchemaTransaction {
|
||||
fn get_inner(&self) -> &SchemaInner {
|
||||
// Does this deref the CowCell for us?
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1124,6 +1150,7 @@ mod tests {
|
|||
use super::super::error::SchemaError;
|
||||
use super::super::filter::Filter;
|
||||
use super::{IndexType, Schema, SchemaAttribute, SchemaClass, SyntaxType};
|
||||
use schema::SchemaReadTransaction;
|
||||
use serde_json;
|
||||
use std::convert::TryFrom;
|
||||
use uuid::Uuid;
|
||||
|
@ -1373,10 +1400,11 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
schema.validate_entry(&e_attr_invalid),
|
||||
Err(SchemaError::MissingMustAttribute(String::from("system")))
|
||||
);
|
||||
let res = schema.validate_entry(&e_attr_invalid);
|
||||
assert!(match res {
|
||||
Err(SchemaError::MissingMustAttribute(_)) => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let e_attr_invalid_may: Entry = serde_json::from_str(
|
||||
r#"{
|
||||
|
|
|
@ -12,11 +12,13 @@ use be::{
|
|||
use constants::{JSON_ANONYMOUS_V1, JSON_SYSTEM_INFO_V1};
|
||||
use entry::Entry;
|
||||
use error::OperationError;
|
||||
use event::{CreateEvent, ExistsEvent, OpResult, SearchEvent, SearchResult, DeleteEvent};
|
||||
use event::{CreateEvent, ExistsEvent, OpResult, SearchEvent, SearchResult, DeleteEvent, ModifyEvent};
|
||||
use filter::Filter;
|
||||
use log::EventLog;
|
||||
use plugins::Plugins;
|
||||
use schema::{Schema, SchemaTransaction, SchemaWriteTransaction};
|
||||
use schema::{Schema, SchemaTransaction, SchemaReadTransaction, SchemaWriteTransaction};
|
||||
use modify::ModifyList;
|
||||
|
||||
|
||||
pub fn start(log: actix::Addr<EventLog>, path: &str, threads: usize) -> actix::Addr<QueryServer> {
|
||||
let mut audit = AuditScope::new("server_start");
|
||||
|
@ -139,8 +141,13 @@ pub trait QueryServerReadTransaction {
|
|||
res
|
||||
}
|
||||
|
||||
fn internal_search(&self, _au: &mut AuditScope, _filter: Filter) -> Result<Vec<Entry>, OperationError> {
|
||||
unimplemented!()
|
||||
fn internal_search(&self, audit: &mut AuditScope, filter: Filter) -> Result<Vec<Entry>, OperationError> {
|
||||
|
||||
let mut audit_int = AuditScope::new("internal_search");
|
||||
let se = SearchEvent::new_internal(filter);
|
||||
let res = self.search(&mut audit_int, &se);
|
||||
audit.append_scope(audit_int);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,6 +320,90 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> {
|
||||
// Get the candidates.
|
||||
// Modify applies a modlist to a filter, so we need to internal search
|
||||
// then apply.
|
||||
// WARNING! Check access controls here!!!!
|
||||
// How can we do the search with the permissions of the caller?
|
||||
|
||||
// TODO: Fix this filter clone ....
|
||||
// Likely this will be fixed if search takes &filter, and then clone
|
||||
// to normalise, instead of attempting to mut the filter on norm.
|
||||
let pre_candidates = match self.internal_search(au, me.filter.clone()) {
|
||||
Ok(results) => results,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
if pre_candidates.len() == 0 {
|
||||
return Err(OperationError::NoMatchingEntries)
|
||||
};
|
||||
|
||||
// Clone a set of writeables.
|
||||
// Apply the modlist -> Remember, we have a set of origs
|
||||
// and the new modified ents.
|
||||
let mut candidates: Vec<Entry> = pre_candidates.iter()
|
||||
.map(|er| {
|
||||
er.apply_modlist(&me.modlist).unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
audit_log!(au, "modify: candidates -> {:?}", candidates);
|
||||
|
||||
// Pre mod plugins
|
||||
|
||||
// Schema validate
|
||||
let r = candidates.iter().fold(Ok(()), |acc, e| {
|
||||
if acc.is_ok() {
|
||||
self.schema
|
||||
.validate_entry(e)
|
||||
.map_err(|err| {
|
||||
audit_log!(au, "Schema Violation: {:?}", err);
|
||||
OperationError::SchemaViolation
|
||||
})
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
if r.is_err() {
|
||||
audit_log!(au, "Modify operation failed (schema), {:?}", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Normalise all the data now it's validated.
|
||||
// FIXME: This normalisation COPIES everything, which may be
|
||||
// slow.
|
||||
let norm_cand: Vec<Entry> = candidates
|
||||
.iter()
|
||||
.map(|e| self.schema.normalise_entry(&e))
|
||||
.collect();
|
||||
|
||||
// Backend Modify
|
||||
let mut audit_be = AuditScope::new("backend_modify");
|
||||
|
||||
let res = self
|
||||
.be_txn
|
||||
.modify(&mut audit_be, &norm_cand)
|
||||
.map(|_| ())
|
||||
.map_err(|e| match e {
|
||||
BackendError::EmptyRequest => OperationError::EmptyRequest,
|
||||
BackendError::EntryMissingId => OperationError::InvalidRequestState,
|
||||
});
|
||||
au.append_scope(audit_be);
|
||||
|
||||
if res.is_err() {
|
||||
// be_txn is dropped, ie aborted here.
|
||||
audit_log!(au, "Modify operation failed (backend), {:?}", r);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Post Plugins
|
||||
|
||||
// return
|
||||
audit_log!(au, "Modify operation success");
|
||||
res
|
||||
}
|
||||
|
||||
// These are where searches and other actions are actually implemented. This
|
||||
// is the "internal" version, where we define the event as being internal
|
||||
// only, allowing certain plugin by passes etc.
|
||||
|
@ -343,6 +434,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
res
|
||||
}
|
||||
|
||||
pub fn internal_modify(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
filter: Filter,
|
||||
modlist: ModifyList
|
||||
) -> Result<(), OperationError> {
|
||||
let mut audit_int = AuditScope::new("internal_modify");
|
||||
let me = ModifyEvent::new_internal(filter, modlist);
|
||||
let res = self.modify(&mut audit_int, &me);
|
||||
audit.append_scope(audit_int);
|
||||
res
|
||||
}
|
||||
|
||||
// internal server operation types.
|
||||
// These just wrap the fn create/search etc, but they allow
|
||||
// creating the needed create event with the correct internal flags
|
||||
|
@ -370,20 +474,32 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
// few attributes.
|
||||
//
|
||||
// This will extra classes an attributes alone!
|
||||
let filt = match e.filter_from_attrs(&vec![String::from("name"), String::from("uuid")]) {
|
||||
let filt = match e.filter_from_attrs(&vec![String::from("uuid")]) {
|
||||
Some(f) => f,
|
||||
None => return Err(OperationError::FilterGeneration),
|
||||
};
|
||||
|
||||
// Does it exist? (TODO: Should be search, not exists ...)
|
||||
match self.internal_exists(audit, filt) {
|
||||
Ok(true) => {
|
||||
// it exists. We need to ensure the content now.
|
||||
unimplemented!()
|
||||
}
|
||||
Ok(false) => {
|
||||
// It does not exist. Create it.
|
||||
self.internal_create(audit, vec![e])
|
||||
match self.internal_search(audit, filt.clone()) {
|
||||
Ok(results) => {
|
||||
if results.len() == 0 {
|
||||
// It does not exist. Create it.
|
||||
self.internal_create(audit, vec![e])
|
||||
} else if results.len() == 1 {
|
||||
// If the thing is subset, pass
|
||||
match e.gen_modlist_assert(&self.schema) {
|
||||
Ok(modlist) => {
|
||||
// Apply to &results[0]
|
||||
audit_log!(audit, "Generated modlist -> {:?}", modlist);
|
||||
self.internal_modify(audit, filt, modlist)
|
||||
}
|
||||
Err(e) => {
|
||||
unimplemented!()
|
||||
// No action required.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(OperationError::InvalidDBState)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// An error occured. pass it back up.
|
||||
|
@ -403,7 +519,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
// attributes and classes.
|
||||
|
||||
// Create a filter from the entry for assertion.
|
||||
let filt = match e.filter_from_attrs(&vec![String::from("name"), String::from("uuid")]) {
|
||||
let filt = match e.filter_from_attrs(&vec![String::from("uuid")]) {
|
||||
Some(f) => f,
|
||||
None => return Err(OperationError::FilterGeneration),
|
||||
};
|
||||
|
@ -441,6 +557,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
let e: Entry = serde_json::from_str(JSON_SYSTEM_INFO_V1).unwrap();
|
||||
self.internal_assert_or_create(audit, e)
|
||||
});
|
||||
audit_log!(audit_si, "start_system_info -> result {:?}", res);
|
||||
audit.append_scope(audit_si);
|
||||
assert!(res.is_ok());
|
||||
if res.is_err() {
|
||||
|
@ -453,6 +570,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
let e: Entry = serde_json::from_str(JSON_ANONYMOUS_V1).unwrap();
|
||||
self.internal_migrate_or_create(audit, e)
|
||||
});
|
||||
audit_log!(audit_an, "start_anonymous -> result {:?}", res);
|
||||
audit.append_scope(audit_an);
|
||||
assert!(res.is_ok());
|
||||
if res.is_err() {
|
||||
|
|
Loading…
Reference in a new issue