Large todo cleanup, but not done yet ...

This commit is contained in:
William Brown 2019-07-26 17:13:58 +09:00
parent 8cc25b8374
commit ed99da58d0
20 changed files with 607 additions and 514 deletions

View file

@ -15,13 +15,11 @@
// requirements (also search).
//
// TODO: Purge all reference to acp_allow/deny
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
use std::collections::{BTreeMap, BTreeSet};
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryNormalised, EntryValid};
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryNormalised, EntryReduced, EntryValid};
use crate::error::OperationError;
use crate::filter::{Filter, FilterValid};
use crate::modify::Modify;
@ -518,7 +516,7 @@ pub trait AccessControlsTransaction {
se: &SearchEvent,
// TODO: This could actually take a &mut Vec and modify in place!
entries: Vec<Entry<EntryValid, EntryCommitted>>,
) -> Result<Vec<Entry<EntryValid, EntryCommitted>>, OperationError> {
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
/*
* Super similar to above (could even re-use some parts). Given a set of entries,
* reduce the attribute sets on them to "what is visible". This is ONLY called on
@ -532,8 +530,9 @@ pub trait AccessControlsTransaction {
// interface is beyond me ....
let rec_entry: &Entry<EntryValid, EntryCommitted> = match &se.event.origin {
EventOrigin::Internal => {
audit_log!(audit, "IMPOSSIBLE STATE: Internal search in external interface?! Returning empty for safety.");
// No need to check ACS
return Ok(entries);
return Ok(Vec::new());
}
EventOrigin::User(e) => &e,
};
@ -574,7 +573,7 @@ pub trait AccessControlsTransaction {
// CAN'T see instead.
// For each entry
let allowed_entries: Vec<Entry<EntryValid, EntryCommitted>> = entries
let allowed_entries: Vec<Entry<EntryReduced, EntryCommitted>> = entries
.into_iter()
.map(|e| {
// Get the set of attributes you can see
@ -754,12 +753,11 @@ pub trait AccessControlsTransaction {
// set that apply to the entry that is performing the operation
let scoped_acp: Vec<&AccessControlModify> = related_acp
.iter()
// TODO: I don't like that acm here is a
// &&
.filter_map(|acm| {
.filter_map(|acm: &&AccessControlModify| {
// TODO: We are continually compiling and using these
// in a tight loop, so this is a possible oppurtunity
// to cache or handle these better.
// to cache or handle these filters better - filter compiler
// cache maybe?
let f_val = acm.acp.targetscope.clone();
match f_val.resolve(&me.event) {
Ok(f_res) => {
@ -1189,7 +1187,7 @@ mod tests {
AccessControlSearch, AccessControls, AccessControlsTransaction,
};
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryInvalid, EntryNew};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced};
// use crate::server::QueryServerWriteTransaction;
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent};
@ -1785,10 +1783,14 @@ mod tests {
.search_filter_entry_attributes(&mut audit, $se, res)
.expect("operation failed");
println!("expect --> {:?}", $expect);
// Help the type checker for the expect set.
let expect_set: Vec<Entry<EntryReduced, EntryCommitted>> =
$expect.into_iter().map(|e| e.to_reduced()).collect();
println!("expect --> {:?}", expect_set);
println!("result --> {:?}", reduced);
// should be ok, and same as expect.
assert!(reduced == $expect);
assert!(reduced == expect_set);
}};
}

View file

@ -48,6 +48,7 @@ macro_rules! audit_segment {
let end = Instant::now();
let diff = end.duration_since(start);
audit_log!($au, "duration -> {:?}", diff);
$au.set_duration(diff);
// Return the result. Hope this works!

View file

@ -53,8 +53,8 @@ pub trait BackendTransaction {
) -> Result<Vec<Entry<EntryValid, EntryCommitted>>, OperationError> {
// Do things
// Alloc a vec for the entries.
// FIXME: Make this actually a good size for the result set ...
// FIXME: Actually compute indexes here.
// TODO: Make this actually a good size for the result set ...
// TODO: Actually compute indexes here.
// So to make this use indexes, we can use the filter type and
// destructure it to work out what we need to actually search (if
// possible) to create the candidate set.
@ -101,6 +101,7 @@ pub trait BackendTransaction {
let entries: Result<Vec<Entry<EntryValid, EntryCommitted>>, _> = raw_entries
.iter()
.filter_map(|id_ent| {
// We need the matches here to satisfy the filter map
let db_e = match serde_cbor::from_slice(id_ent.data.as_slice())
.map_err(|_| OperationError::SerdeCborError)
{
@ -113,7 +114,11 @@ pub trait BackendTransaction {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let e = Entry::from_dbentry(db_e, id);
let e =
match Entry::from_dbentry(db_e, id).ok_or(OperationError::CorruptedEntry) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if e.entry_match_no_index(&filt) {
Some(Ok(e))
} else {
@ -224,17 +229,15 @@ pub trait BackendTransaction {
}
impl Drop for BackendReadTransaction {
// Abort
// TODO: Is this correct for RO txn?
// Abort - so far this has proven reliable to use drop here.
fn drop(self: &mut Self) {
if !self.committed {
debug!("Aborting BE RO txn");
self.conn
.execute("ROLLBACK TRANSACTION", NO_PARAMS)
// TODO: Can we do this without expect? I think we can't due
// to the way drop works.
//
// We may need to change how we do transactions to not rely on drop
// We can't do this without expect.
// We may need to change how we do transactions to not rely on drop if
// it becomes and issue :(
.expect("Unable to rollback transaction! Can not proceed!!!");
}
}
@ -244,9 +247,11 @@ impl BackendReadTransaction {
pub fn new(conn: r2d2::PooledConnection<SqliteConnectionManager>) -> Self {
// Start the transaction
debug!("Starting BE RO txn ...");
// TODO: Way to flag that this will be a read only?
// TODO: Can we do this without expect? I think we need to change the type
// signature here if we wanted to...
// I'm happy for this to be an expect, because this is a huge failure
// of the server ... but if it happens a lot we should consider making
// this a Result<>
//
// There is no way to flag this is an RO operation.
conn.execute("BEGIN TRANSACTION", NO_PARAMS)
.expect("Unable to begin transaction!");
BackendReadTransaction {
@ -263,7 +268,6 @@ impl BackendTransaction for BackendReadTransaction {
}
static DBV_ID2ENTRY: &'static str = "id2entry";
// static DBV_INDEX: &'static str = "index";
impl Drop for BackendWriteTransaction {
// Abort
@ -272,10 +276,6 @@ impl Drop for BackendWriteTransaction {
debug!("Aborting BE WR txn");
self.conn
.execute("ROLLBACK TRANSACTION", NO_PARAMS)
// TODO: Can we do this without expect? I think we can't due
// to the way drop works.
//
// We may need to change how we do transactions to not rely on drop
.expect("Unable to rollback transaction! Can not proceed!!!");
}
}
@ -291,9 +291,6 @@ impl BackendWriteTransaction {
pub fn new(conn: r2d2::PooledConnection<SqliteConnectionManager>) -> Self {
// Start the transaction
debug!("Starting BE WR txn ...");
// TODO: Way to flag that this will be a write?
// TODO: Can we do this without expect? I think we need to change the type
// signature here if we wanted to...
conn.execute("BEGIN TRANSACTION", NO_PARAMS)
.expect("Unable to begin transaction!");
BackendWriteTransaction {
@ -347,11 +344,7 @@ impl BackendWriteTransaction {
})
.collect();
// audit_log!(au, "serialising: {:?}", ser_entries);
let ser_entries = ser_entries?;
// THIS IS PROBABLY THE BIT WHERE YOU NEED DB ABSTRACTION
{
let mut stmt = try_audit!(
au,
@ -388,8 +381,10 @@ impl BackendWriteTransaction {
// tell which function is calling it. either this one or restore.
audit_segment!(au, || {
if entries.is_empty() {
// TODO: Better error
// End the timer
audit_log!(
au,
"No entries provided to BE to create, invalid server call!"
);
return Err(OperationError::EmptyRequest);
}
@ -411,6 +406,10 @@ impl BackendWriteTransaction {
entries: &Vec<Entry<EntryValid, EntryCommitted>>,
) -> Result<(), OperationError> {
if entries.is_empty() {
audit_log!(
au,
"No entries provided to BE to modify, invalid server call!"
);
return Err(OperationError::EmptyRequest);
}
@ -434,8 +433,10 @@ impl BackendWriteTransaction {
let data = serde_cbor::to_vec(&db_e).map_err(|_| OperationError::SerdeCborError)?;
Ok(IdEntry {
// TODO: Instead of getting these from the entry, we could lookup
// TODO: Instead of getting these from the server entry struct , we could lookup
// uuid -> id in the index.
//
// relies on the uuid -> id index being correct (and implemented)
id: id,
data: data,
})
@ -447,7 +448,9 @@ impl BackendWriteTransaction {
// 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.
//
// The entry state checks prevent this from really ever being triggered, but we
// still prefer paranoia :)
if entries.len() != ser_entries.len() {
return Err(OperationError::InvalidEntryState);
}
@ -483,7 +486,10 @@ impl BackendWriteTransaction {
// Perform a search for the entries --> This is a problem for the caller
audit_segment!(au, || {
if entries.is_empty() {
// TODO: Better error
audit_log!(
au,
"No entries provided to BE to delete, invalid server call!"
);
return Err(OperationError::EmptyRequest);
}
@ -506,7 +512,6 @@ impl BackendWriteTransaction {
let id_list = try_audit!(au, id_list);
// 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(OperationError::InvalidEntryState);
}
@ -516,8 +521,6 @@ impl BackendWriteTransaction {
// SQL doesn't say if the thing "does or does not exist anymore". As a result,
// two deletes is a safe and valid operation. Given how we allocate ID's we are
// probably okay with this.
// TODO: ACTUALLY HANDLE THIS ERROR WILLIAM YOU LAZY SHIT.
let mut stmt = try_audit!(
au,
self.conn.prepare("DELETE FROM id2entry WHERE id = :id"),
@ -571,11 +574,15 @@ impl BackendWriteTransaction {
OperationError::SerdeJsonError
);
self.internal_create(audit, &entries)
self.internal_create(audit, &entries)?;
let vr = self.verify();
if vr.len() == 0 {
Ok(())
} else {
Err(OperationError::ConsistencyError(vr))
}
// TODO: run re-index after db is restored
// TODO; run db verify
// self.verify(audit)
}
pub fn commit(mut self) -> Result<(), OperationError> {
@ -611,7 +618,7 @@ impl BackendWriteTransaction {
{
// TODO:
// conn.execute("PRAGMA journal_mode=WAL;", NO_PARAMS)
//
// This stores versions of components. For example:
// ----------------------
// | id | version |
@ -694,7 +701,7 @@ impl Backend {
// a single DB thread, else we cause consistency issues.
builder1.max_size(1)
} else {
// FIXME: Make this configurable
// TODO: Make this configurable
builder1.max_size(8)
};
// Look at max_size and thread_pool here for perf later
@ -717,7 +724,6 @@ impl Backend {
}
pub fn read(&self) -> BackendReadTransaction {
// TODO: Don't use expect
let conn = self
.pool
.get()
@ -726,7 +732,6 @@ impl Backend {
}
pub fn write(&self) -> BackendWriteTransaction {
// TODO: Don't use expect
let conn = self
.pool
.get()

View file

@ -117,6 +117,8 @@ pub static JSON_IDM_SELF_ACP_READ_V1: &'static str = r#"{
}
}"#;
pub static UUID_DOES_NOT_EXIST: &'static str = "00000000-0000-0000-0000-fffffffffffe";
pub static UUID_ANONYMOUS: &'static str = "00000000-0000-0000-0000-ffffffffffff";
pub static JSON_ANONYMOUS_V1: &'static str = r#"{
"valid": {
@ -133,12 +135,12 @@ pub static JSON_ANONYMOUS_V1: &'static str = r#"{
}"#;
// Core
// TODO: All these should be internal range uuids instead.
pub static UUID_SCHEMA_ATTR_CLASS: &'static str = "aa0f193f-3010-4783-9c9e-f97edb14d8c2";
pub static UUID_SCHEMA_ATTR_UUID: &'static str = "642a893b-fe1a-4fe1-805d-fb78e7f83ee7";
pub static UUID_SCHEMA_ATTR_NAME: &'static str = "27be9127-5ba1-4c06-bce9-7250f2c7f630";
pub static UUID_SCHEMA_ATTR_PRINCIPAL_NAME: &'static str = "64dda3ac-12cb-4000-9b30-97a92767ccab";
pub static UUID_SCHEMA_ATTR_DESCRIPTION: &'static str = "a4da35a2-c5fb-4f8f-a341-72cd39ec9eee";
pub static UUID_SCHEMA_ATTR_SECRET: &'static str = "0231c61a-0a43-4987-9293-8732ed9459fa";
pub static UUID_SCHEMA_ATTR_MULTIVALUE: &'static str = "8a6a8bf3-7053-42e2-8cda-15af7a197513";
pub static UUID_SCHEMA_ATTR_INDEX: &'static str = "2c5ff455-0709-4f67-a37c-35ff7e67bfff";
pub static UUID_SCHEMA_ATTR_SYNTAX: &'static str = "85e8c2c7-3852-48dd-bfc9-d0982a50e2ef";
@ -213,9 +215,6 @@ pub static JSON_SCHEMA_ATTR_DISPLAYNAME: &'static str = r#"{
"name": [
"displayname"
],
"secret": [
"false"
],
"syntax": [
"UTF8STRING"
],
@ -249,9 +248,6 @@ pub static JSON_SCHEMA_ATTR_MAIL: &'static str = r#"
"name": [
"mail"
],
"secret": [
"false"
],
"syntax": [
"UTF8STRING"
],
@ -284,9 +280,6 @@ pub static JSON_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = r#"
"name": [
"ssh_publickey"
],
"secret": [
"false"
],
"syntax": [
"UTF8STRING"
],
@ -319,9 +312,6 @@ pub static JSON_SCHEMA_ATTR_PASSWORD: &'static str = r#"
"name": [
"password"
],
"secret": [
"true"
],
"syntax": [
"UTF8STRING"
],

View file

@ -221,13 +221,12 @@ fn auth(
}
}
}
AuthState::Denied => {
AuthState::Denied(_) => {
// Remove the auth-session-id
req.session().remove("auth-session-id");
Ok(HttpResponse::Ok().json(ar))
}
AuthState::Continue(_) => {
// TODO: Where do we get the auth-session-id from?
// Ensure the auth-session-id is set
match req
.session()

View file

@ -153,6 +153,9 @@ pub struct EntryInvalid;
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct EntryNormalised;
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct EntryReduced;
#[derive(Debug, Serialize, Deserialize)]
pub struct Entry<VALID, STATE> {
valid: VALID,
@ -177,7 +180,11 @@ impl Entry<EntryInvalid, EntryNew> {
}
}
// FIXME: Can we consume protoentry?
// Could we consume protoentry?
//
// I think we could, but that would limit us to how protoentry works,
// where we are likely to actually change the Entry type here and how
// we store and represent types and data.
pub fn from_proto_entry(
audit: &mut AuditScope,
e: &ProtoEntry,
@ -189,7 +196,6 @@ impl Entry<EntryInvalid, EntryNew> {
// Somehow we need to take the tree of e attrs, and convert
// all ref types to our types ...
let map2: Result<BTreeMap<String, Vec<String>>, OperationError> = e
.attrs
.iter()
@ -245,12 +251,7 @@ impl<STATE> Entry<EntryNormalised, STATE> {
};
// Now validate it!
// First look at the classes on the entry.
// Now, check they are valid classes
//
// FIXME: We could restructure this to be a map that gets Some(class)
// if found, then do a len/filter/check on the resulting class set?
// We scope here to limit the time of borrow of ne.
{
// First, check we have class on the object ....
if !ne.attribute_pres("class") {
@ -258,14 +259,14 @@ impl<STATE> Entry<EntryNormalised, STATE> {
return Err(SchemaError::InvalidClass);
}
let entry_classes = ne.classes();
// Do we have extensible?
let extensible = ne.attribute_value_pres("class", "extensibleobject");
let entry_classes = ne.classes().ok_or(SchemaError::InvalidClass)?;
let entry_classes_size = entry_classes.len();
let classes: HashMap<String, &SchemaClass> = entry_classes
.filter_map(|c| match schema_classes.get(c) {
Some(cls) => Some((c.clone(), cls)),
None => None,
})
let classes: Vec<&SchemaClass> = entry_classes
.filter_map(|c| schema_classes.get(c))
.collect();
if classes.len() != entry_classes_size {
@ -273,32 +274,26 @@ impl<STATE> Entry<EntryNormalised, STATE> {
return Err(SchemaError::InvalidClass);
};
let extensible = classes.contains_key("extensibleobject");
// What this is really doing is taking a set of classes, and building an
// "overall" class that describes this exact object for checking
// "overall" class that describes this exact object for checking. IE we
// build a super must/may set from the small class must/may sets.
// for each class
// add systemmust/must and systemmay/may to their lists
// add anything from must also into may
// Now from the set of valid classes make a list of must/may
// FIXME: This is clone on read, which may be really slow. It also may
// be inefficent on duplicates etc.
//
// NOTE: We still need this on extensible, because we still need to satisfy
// our other must conditions too!
let must: Result<HashMap<String, &SchemaAttribute>, _> = classes
// our other must conditions as well!
let must: Result<Vec<&SchemaAttribute>, _> = classes
.iter()
// Join our class systemmmust + must into one iter
.flat_map(|(_, cls)| cls.systemmust.iter().chain(cls.must.iter()))
.flat_map(|cls| cls.systemmust.iter().chain(cls.must.iter()))
.map(|s| {
// This should NOT fail - if it does, it means our schema is
// in an invalid state!
Ok((
s.clone(),
schema_attributes.get(s).ok_or(SchemaError::Corrupted)?,
))
Ok(schema_attributes.get(s).ok_or(SchemaError::Corrupted)?)
})
.collect();
@ -306,11 +301,10 @@ impl<STATE> Entry<EntryNormalised, STATE> {
// Check that all must are inplace
// for each attr in must, check it's present on our ent
// FIXME: Could we iter over only the attr_name
for (attr_name, _attr) in must {
let avas = ne.get_ava(&attr_name);
for attr in must {
let avas = ne.get_ava(&attr.name);
if avas.is_none() {
return Err(SchemaError::MissingMustAttribute(String::from(attr_name)));
return Err(SchemaError::MissingMustAttribute(attr.name.clone()));
}
}
@ -338,10 +332,13 @@ impl<STATE> Entry<EntryNormalised, STATE> {
}
}
} else {
// TODO: Again, we clone string here, but it's so we can check all
// the values in "may" ar here - so we can't avoid this look up. What we
// could do though, is have &String based on the schemaattribute though?;
let may: Result<HashMap<String, &SchemaAttribute>, _> = classes
.iter()
// Join our class systemmmust + must + systemmay + may into one.
.flat_map(|(_, cls)| {
.flat_map(|cls| {
cls.systemmust
.iter()
.chain(cls.must.iter())
@ -360,8 +357,9 @@ impl<STATE> Entry<EntryNormalised, STATE> {
let may = may?;
// FIXME: Error needs to say what is missing
// We need to return *all* missing attributes.
// TODO: Error needs to say what is missing
// We need to return *all* missing attributes, not just the first error
// we find.
// Check that any other attributes are in may
// for each attr on the object, check it's in the may+must set
@ -646,28 +644,37 @@ impl Entry<EntryValid, EntryCommitted> {
self.state.id
}
pub fn from_dbentry(db_e: DbEntry, id: u64) -> Self {
pub fn from_dbentry(db_e: DbEntry, id: u64) -> Option<Self> {
let attrs = match db_e.ent {
DbEntryVers::V1(v1) => v1.attrs,
};
// TODO: Tidy this!
let uuid: String = match attrs.get("uuid") {
Some(vs) => vs.first(),
None => None,
}
.expect("NO UUID PRESENT CORRUPT")
}?
.clone();
Entry {
Some(Entry {
valid: EntryValid { uuid: uuid },
state: EntryCommitted { id },
attrs: attrs,
})
}
#[cfg(test)]
pub fn to_reduced(self) -> Entry<EntryReduced, EntryCommitted> {
Entry {
valid: EntryReduced,
state: self.state,
attrs: self.attrs,
}
}
pub fn reduce_attributes(self, allowed_attrs: BTreeSet<&str>) -> Self {
// TODO: Make this inplace, not copying.
pub fn reduce_attributes(
self,
allowed_attrs: BTreeSet<&str>,
) -> Entry<EntryReduced, EntryCommitted> {
// Remove all attrs from our tree that are NOT in the allowed set.
let Entry {
@ -688,7 +695,7 @@ impl Entry<EntryValid, EntryCommitted> {
.collect();
Entry {
valid: s_valid,
valid: EntryReduced,
state: s_state,
attrs: f_attrs,
}
@ -808,21 +815,6 @@ impl<STATE> Entry<EntryValid, STATE> {
)))
}
// FIXME: This should probably have an entry state for "reduced"
// and then only that state can provide the into_pe type, so that we
// can guarantee that all entries must have been security checked.
pub fn into_pe(&self) -> ProtoEntry {
// It's very likely that at this stage we'll need to apply
// access controls, dynamic attributes or more.
// As a result, this may not even be the right place
// for the conversion as algorithmically it may be
// better to do this from the outside view. This can
// of course be identified and changed ...
ProtoEntry {
attrs: self.attrs.clone(),
}
}
pub fn gen_modlist_assert(
&self,
schema: &SchemaTransaction,
@ -844,6 +836,9 @@ impl<STATE> Entry<EntryValid, STATE> {
// check can "go away" because uuid will never exist as an ava.
//
// TODO: Remove this check when uuid becomes a real attribute.
// UUID is now a real attribute, but it also has an ava for db_entry
// conversion - so what do? If we remove it here, we could have CSN issue with
// repl on uuid conflict, but it probably shouldn't be an ava either ...
if k == "uuid" {
continue;
}
@ -868,9 +863,29 @@ impl<STATE> Entry<EntryValid, STATE> {
}
}
impl Entry<EntryReduced, EntryCommitted> {
pub fn into_pe(&self) -> ProtoEntry {
// It's very likely that at this stage we'll need to apply
// access controls, dynamic attributes or more.
// As a result, this may not even be the right place
// for the conversion as algorithmically it may be
// better to do this from the outside view. This can
// of course be identified and changed ...
ProtoEntry {
attrs: self.attrs.clone(),
}
}
}
// impl<STATE> Entry<EntryValid, STATE> {
impl<VALID, STATE> Entry<VALID, STATE> {
/* WARNING: Should these TODO move to EntryValid only? */
// TODO: Should this be Vec<&str>?
/*
* WARNING: Should these TODO move to EntryValid only?
* I've tried to do this once, but the issue is that there
* is a lot of code in normalised and other states that
* relies on the ability to get ava. I think we may not be
* able to do so "easily".
*/
pub fn get_ava(&self, attr: &str) -> Option<&Vec<String>> {
self.attrs.get(attr)
}
@ -904,11 +919,23 @@ impl<VALID, STATE> Entry<VALID, STATE> {
}
pub fn attribute_pres(&self, attr: &str) -> bool {
// FIXME: Do we need to normalise attr name?
// Note, we don't normalise attr name, but I think that's not
// something we should over-optimise on.
self.attrs.contains_key(attr)
}
pub fn attribute_value_pres(&self, attr: &str, value: &str) -> bool {
// Yeah, this is techdebt, but both names of this fn are valid - we are
// checking if an attribute-value is equal to, or asserting it's present
// as a pair. So I leave both, and let the compiler work it out.
self.attribute_equality(attr, value)
}
pub fn attribute_equality(&self, attr: &str, value: &str) -> bool {
// we assume based on schema normalisation on the way in
// that the equality here of the raw values MUST be correct.
// We also normalise filters, to ensure that their values are
// syntax valid and will correctly match here with our indexes.
match self.attrs.get(attr) {
Some(v_list) => match v_list.binary_search(&value.to_string()) {
Ok(_) => true,
@ -918,35 +945,23 @@ impl<VALID, STATE> Entry<VALID, STATE> {
}
}
pub fn attribute_equality(&self, attr: &str, value: &str) -> bool {
// we assume based on schema normalisation on the way in
// that the equality here of the raw values MUST be correct.
// We also normalise filters, to ensure that their values are
// syntax valid and will correctly match here with our indexes.
// 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 attribute_substring(&self, attr: &str, subvalue: &str) -> bool {
match self.attrs.get(attr) {
Some(v_list) => {
v_list
.iter()
.fold(false, |acc, v| if acc { acc } else { v.contains(subvalue) })
}
None => false,
}
}
pub fn attribute_substring(&self, _attr: &str, _subvalue: &str) -> bool {
unimplemented!();
}
pub fn classes(&self) -> EntryClasses {
pub fn classes(&self) -> Option<EntryClasses> {
// Get the class vec, if any?
// How do we indicate "empty?"
// FIXME: Actually handle this error ...
let v = self
.attrs
.get("class")
.map(|c| c.len())
.expect("INVALID STATE, NO CLASS FOUND");
let v = self.attrs.get("class").map(|c| c.len())?;
let c = self.attrs.get("class").map(|c| c.iter());
EntryClasses { size: v, inner: c }
Some(EntryClasses { size: v, inner: c })
}
pub fn avas(&self) -> EntryAvas {
@ -1001,22 +1016,20 @@ where
// a list of syntax violations ...
// 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: &str, value: &str) {
// get_mut to access value
// How do we make this turn into an ok / err?
self.attrs
.entry(attr.to_string())
.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
// TODO: Is there a way to avoid the double to_string here?
match v.binary_search(&value.to_string()) {
// It already exists, done!
Ok(_) => {}
Err(idx) => {
// This cloning is to fix a borrow issue with the or_insert below.
// Is there a better way?
//
// I think it's only run once anyway, so non-issue?
v.insert(idx, value.to_string())
}
}
@ -1025,31 +1038,26 @@ where
}
pub fn remove_ava(&mut self, attr: &str, value: &str) {
// TODO fix this to_string
// It would be great to remove these extra allocations, but they
// really don't cost much :(
let mv = value.to_string();
self.attrs
// TODO: Fix this to_string
.entry(attr.to_string())
.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(&mv) {
// It exists, rm it.
Ok(idx) => {
v.remove(idx);
}
// It does not exist, move on.
Err(_) => {}
self.attrs.entry(attr.to_string()).and_modify(|v| {
// Here we need to actually do a check/binary search ...
match v.binary_search(&mv) {
// It exists, rm it.
Ok(idx) => {
v.remove(idx);
}
});
// It does not exist, move on.
Err(_) => {}
}
});
}
pub fn purge_ava(&mut self, attr: &str) {
self.attrs.remove(attr);
}
// FIXME: Should this collect from iter instead?
// TODO: Should this be a Vec<&str>
/// Overwrite the existing avas.
pub fn set_avas(&mut self, attr: &str, values: Vec<String>) {
// Overwrite the existing value
@ -1063,6 +1071,7 @@ where
}
// Should this be schemaless, relying on checks of the modlist, and the entry validate after?
// YES. Makes it very cheap.
pub fn apply_modlist(&mut self, modlist: &ModifyList<ModifyValid>) {
// -> Result<Entry<EntryInvalid, STATE>, OperationError> {
// Apply a modlist, generating a new entry that conforms to the changes.
@ -1104,12 +1113,6 @@ impl From<&SchemaAttribute> for Entry<EntryValid, EntryNew> {
let name_v = vec![s.name.clone()];
let desc_v = vec![s.description.clone()];
let secret_v = vec![if s.secret {
"true".to_string()
} else {
"false".to_string()
}];
let multivalue_v = vec![if s.multivalue {
"true".to_string()
} else {
@ -1125,7 +1128,6 @@ impl From<&SchemaAttribute> for Entry<EntryValid, EntryNew> {
attrs.insert("name".to_string(), name_v);
attrs.insert("description".to_string(), desc_v);
attrs.insert("uuid".to_string(), uuid_v);
attrs.insert("secret".to_string(), secret_v);
attrs.insert("multivalue".to_string(), multivalue_v);
attrs.insert("index".to_string(), index_v);
attrs.insert("syntax".to_string(), syntax_v);
@ -1233,6 +1235,21 @@ mod tests {
assert!(!e.attribute_equality("nonexist", "william"));
}
#[test]
fn test_entry_substring() {
let mut e: Entry<EntryInvalid, EntryNew> = Entry::new();
e.add_ava("userid", "william");
assert!(e.attribute_substring("userid", "william"));
assert!(e.attribute_substring("userid", "will"));
assert!(e.attribute_substring("userid", "liam"));
assert!(e.attribute_substring("userid", "lli"));
assert!(!e.attribute_substring("userid", "llim"));
assert!(!e.attribute_substring("userid", "bob"));
assert!(!e.attribute_substring("userid", "wl"));
}
#[test]
fn test_entry_apply_modlist() {
// Test application of changes to an entry.

View file

@ -4,8 +4,6 @@
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(String),
InvalidAttribute,
InvalidAttributeSyntax,
@ -18,6 +16,7 @@ pub enum OperationError {
EmptyRequest,
Backend,
NoMatchingEntries,
CorruptedEntry,
ConsistencyError(Vec<Result<(), ConsistencyError>>),
SchemaViolation(SchemaError),
Plugin,

View file

@ -1,5 +1,5 @@
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
use crate::filter::{Filter, FilterValid};
use crate::proto::v1::Entry as ProtoEntry;
use crate::proto::v1::{
@ -30,7 +30,7 @@ use actix::prelude::*;
use uuid::Uuid;
// Should the event Result have the log items?
// FIXME: Remove seralising here - each type should
// TODO: Remove seralising here - each type should
// have it's own result type!
// TODO: Every event should have a uuid for logging analysis
@ -50,7 +50,7 @@ pub struct SearchResult {
}
impl SearchResult {
pub fn new(entries: Vec<Entry<EntryValid, EntryCommitted>>) -> Self {
pub fn new(entries: Vec<Entry<EntryReduced, EntryCommitted>>) -> Self {
SearchResult {
entries: entries
.iter()
@ -119,7 +119,7 @@ impl Event {
let uat = uat.ok_or(OperationError::NotAuthenticated)?;
let e = try_audit!(audit, qs.internal_search_uuid(audit, uat.uuid.as_str()));
// FIXME: Now apply claims from the uat into the Entry
// TODO: Now apply claims from the uat into the Entry
// to allow filtering.
Ok(Event {
@ -342,7 +342,7 @@ pub struct CreateEvent {
// This may affect which plugins are run ...
}
// FIXME: Should this actually be in createEvent handler?
// TODO: Should this actually be in createEvent handler?
impl CreateEvent {
pub fn from_request(
audit: &mut AuditScope,
@ -679,7 +679,7 @@ pub struct WhoamiResult {
}
impl WhoamiResult {
pub fn new(e: Entry<EntryValid, EntryCommitted>) -> Self {
pub fn new(e: Entry<EntryReduced, EntryCommitted>) -> Self {
WhoamiResult {
youare: e.into_pe(),
}

View file

@ -13,7 +13,7 @@ enum CredState {
Success(Vec<Claim>),
Continue(Vec<AuthAllowed>),
// TODO: Should we have a reason in Denied so that we
Denied,
Denied(&'static str),
}
#[derive(Clone, Debug)]
@ -35,24 +35,27 @@ impl CredHandler {
pub fn validate(&mut self, creds: &Vec<AuthCredential>) -> CredState {
match self {
CredHandler::Anonymous => {
creds.iter().fold(CredState::Denied, |acc, cred| {
// TODO: if denied, continue returning denied.
// TODO: if continue, contunue returning continue.
// How to do this correctly?
creds.iter().fold(
CredState::Denied("non-anonymous credential provided"),
|acc, cred| {
// TODO: if denied, continue returning denied.
// TODO: if continue, contunue returning continue.
// How to do this correctly?
// There is no "continuation" from this type.
match cred {
AuthCredential::Anonymous => {
// For anonymous, no claims will ever be issued.
CredState::Success(Vec::new())
// There is no "continuation" from this type.
match cred {
AuthCredential::Anonymous => {
// For anonymous, no claims will ever be issued.
CredState::Success(Vec::new())
}
_ => {
// Should we have a reason in Denied so that we can say why denied?
acc
// CredState::Denied
}
}
_ => {
// Should we have a reason in Denied so that we can say why denied?
acc
// CredState::Denied
}
}
})
},
)
}
}
}
@ -137,9 +140,9 @@ impl AuthSession {
Ok(AuthState::Success(uat))
}
CredState::Continue(allowed) => Ok(AuthState::Continue(allowed)),
CredState::Denied => {
CredState::Denied(reason) => {
self.finished = true;
Ok(AuthState::Denied)
Ok(AuthState::Denied(reason.to_string()))
}
}
// Also send an async message to self to log the auth as provided.

View file

@ -13,12 +13,12 @@ use uuid::Uuid;
// use lru::LruCache;
pub struct IdmServer {
// Need an auth-session table to save in progress authentications
// sessions:
// There is a good reason to keep this single thread - it
// means that limits to sessions can be easily applied and checked to
// variaous accounts, and we have a good idea of how to structure the
// in memory caches related to locking.
//
// TODO: This should be Lru
// TODO: AuthSession should be per-session mutex to keep locking on the
// cell low to allow more concurrent auths.
// TODO: This needs a mark-and-sweep gc to be added.
sessions: CowCell<BTreeMap<Uuid, AuthSession>>,
// Need a reference to the query server.
qs: QueryServer,
@ -60,7 +60,6 @@ impl IdmServer {
}
impl<'a> IdmServerWriteTransaction<'a> {
// TODO: This should be something else, not the proto token!
pub fn auth(
&mut self,
au: &mut AuditScope,

View file

@ -1,8 +1,10 @@
use crate::plugins::Plugin;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::ops::Bound::Included;
use uuid::Uuid;
use crate::audit::AuditScope;
use crate::constants::{UUID_ADMIN, UUID_ANONYMOUS, UUID_DOES_NOT_EXIST};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
use crate::error::{ConsistencyError, OperationError};
use crate::event::{CreateEvent, ModifyEvent};
@ -11,13 +13,6 @@ use crate::server::{
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
};
// TO FINISH
/*
Add normalisation step
Add filter normaliser to search.
Add principal name generation
*/
// This module has some special properties around it's operation, namely that it
// has to make a certain number of assertions *early* in the entry lifecycle around
// names and uuids since these have such signifigance to every other part of the
@ -43,9 +38,9 @@ impl Plugin for Base {
fn pre_create_transform(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
ce: &CreateEvent,
) -> Result<(), OperationError> {
// For each candidate
for entry in cand.iter_mut() {
@ -59,17 +54,16 @@ impl Plugin for Base {
// If they have a name, but no principal name, derive it.
// if they don't have uuid, create it.
// TODO: get_ava should have a str version for effeciency?
let c_uuid: String = match entry.get_ava("uuid") {
Some(u) => {
// Actually check we have a value, could be empty array ...
// TODO: Should this be left to schema to assert the value?
if u.len() > 1 {
audit_log!(au, "Entry defines uuid attr, but multiple values.");
return Err(OperationError::Plugin);
};
// Schema of the value v, is checked in the filter generation. Neat!
// That way we don't need to check it here either.
// Should this be forgiving and just generate the UUID?
// NO! If you tried to specify it, but didn't give it, then you made
@ -90,7 +84,7 @@ impl Plugin for Base {
}
// Now, every cand has a UUID - create a cand uuid set from it.
let mut cand_uuid: BTreeMap<&String, ()> = BTreeMap::new();
let mut cand_uuid: BTreeSet<&str> = BTreeSet::new();
// As we insert into the set, if a duplicate is found, return an error
// that a duplicate exists.
@ -101,26 +95,53 @@ impl Plugin for Base {
.first()
.ok_or(OperationError::Plugin)?;
audit_log!(au, "Entry valid UUID: {:?}", entry);
match cand_uuid.insert(uuid_ref, ()) {
Some(v) => {
audit_log!(au, "uuid duplicate found in create set! {:?}", v);
match cand_uuid.insert(uuid_ref.as_str()) {
false => {
audit_log!(au, "uuid duplicate found in create set! {:?}", uuid_ref);
return Err(OperationError::Plugin);
}
None => {}
true => {}
}
}
// Check that the system-protected range is not in the cand_uuid, unless we are
// an internal operation.
if !ce.event.is_internal() {
// The internal set is bounded by: UUID_ADMIN -> UUID_ANONYMOUS
// Sadly we need to allocate these to strings to make references, sigh.
// let uuid_admin: String = UUID_ADMIN.to_string();
// let uuid_anon: String = UUID_ANONYMOUS.to_string();
let overlap: usize = cand_uuid.range(UUID_ADMIN..UUID_ANONYMOUS).count();
if overlap != 0 {
audit_log!(
au,
"uuid from protected system UUID range found in create set! {:?}",
overlap
);
return Err(OperationError::Plugin);
}
}
if cand_uuid.contains(UUID_DOES_NOT_EXIST) {
audit_log!(
au,
"uuid \"does not exist\" found in create set! {:?}",
UUID_DOES_NOT_EXIST
);
return Err(OperationError::Plugin);
}
// Now from each element, generate a filter to search for all of them
//
// NOTE: We don't exclude recycled or tombstones here!
let filt_in = filter_all!(FC::Or(cand_uuid.keys().map(|u| f_eq("uuid", u)).collect(),));
// IMPORTANT: We don't exclude recycled or tombstones here!
let filt_in = filter_all!(FC::Or(cand_uuid.iter().map(|u| f_eq("uuid", u)).collect(),));
// If any results exist, fail as a duplicate UUID is present.
// TODO: Can we report which UUID exists? Probably yes, we do
// internal searh and report the UUID *OR* we alter internal_exists
// internal search and report the UUID *OR* we alter internal_exists
// to return UUID sets.
//
// But does it add value? How many people will try to custom define/add uuid?
let mut au_qs = AuditScope::new("qs_exist");
let r = qs.internal_exists(&mut au_qs, filt_in);
au.append_scope(au_qs);
@ -143,7 +164,7 @@ impl Plugin for Base {
fn pre_modify(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
me: &ModifyEvent,
) -> Result<(), OperationError> {
@ -225,12 +246,44 @@ impl Plugin for Base {
mod tests {
// #[macro_use]
// use crate::plugins::Plugin;
use crate::constants::JSON_ADMIN_V1;
use crate::entry::{Entry, EntryInvalid, EntryNew};
use crate::error::OperationError;
use crate::modify::{Modify, ModifyList};
use crate::server::QueryServerTransaction;
use crate::server::QueryServerWriteTransaction;
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
"valid": null,
"state": null,
"attrs": {
"class": [
"object",
"access_control_profile",
"access_control_modify",
"access_control_create",
"access_control_delete",
"access_control_search"
],
"name": ["idm_admins_acp_allow_all_test"],
"uuid": ["bb18f746-a409-497d-928c-5455d4aef4f7"],
"description": ["Builtin IDM Administrators Access Controls."],
"acp_enable": ["true"],
"acp_receiver": [
"{\"Eq\":[\"uuid\",\"00000000-0000-0000-0000-000000000000\"]}"
],
"acp_targetscope": [
"{\"Pres\":\"class\"}"
],
"acp_search_attr": ["name", "class", "uuid"],
"acp_modify_class": ["system"],
"acp_modify_removedattr": ["class", "displayname", "may", "must"],
"acp_modify_presentattr": ["class", "displayname", "may", "must"],
"acp_create_class": ["object", "person", "system"],
"acp_create_attr": ["name", "class", "description", "displayname", "uuid"]
}
}"#;
// check create where no uuid
#[test]
fn test_pre_create_no_uuid() {
@ -572,4 +625,71 @@ mod tests {
|_, _| {}
);
}
#[test]
fn test_protected_uuid_range() {
// Test an external create, it should fail.
// Testing internal create is not super needed, due to migrations at start
// up testing this every time we run :P
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let preload = vec![acp];
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
"valid": null,
"state": null,
"attrs": {
"class": ["person", "system"],
"name": ["testperson"],
"uuid": ["00000000-0000-0000-0000-f0f0f0f0f0f0"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::Plugin),
preload,
create,
Some(JSON_ADMIN_V1),
|_, _| {}
);
}
#[test]
fn test_protected_uuid_does_not_exist() {
// Test that internal create of "does not exist" will fail.
let preload = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
"valid": null,
"state": null,
"attrs": {
"class": ["person", "system"],
"name": ["testperson"],
"uuid": ["00000000-0000-0000-0000-fffffffffffe"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::Plugin),
preload,
create,
None,
|_, _| {}
);
}
}

View file

@ -16,7 +16,7 @@ macro_rules! setup_test {
qs.initialise_helper($au).expect("init failed!");
if !$preload_entries.is_empty() {
let qs_write = qs.write();
let mut qs_write = qs.write();
qs_write
.internal_create($au, $preload_entries)
.expect("Failed to preload entries");
@ -56,7 +56,7 @@ macro_rules! run_create_test {
let mut au_test = AuditScope::new("create_test");
{
let qs_write = qs.write();
let mut qs_write = qs.write();
let r = qs_write.create(&mut au_test, &ce);
debug!("r: {:?}", r);
assert!(r == $expect);
@ -110,7 +110,7 @@ macro_rules! run_modify_test {
let mut au_test = AuditScope::new("modify_test");
{
let qs_write = qs.write();
let mut qs_write = qs.write();
let r = qs_write.modify(&mut au_test, &me);
$check(&mut au_test, &qs_write);
assert!(r == $expect);
@ -162,7 +162,7 @@ macro_rules! run_delete_test {
let mut au_test = AuditScope::new("delete_test");
{
let qs_write = qs.write();
let mut qs_write = qs.write();
let r = qs_write.delete(&mut au_test, &de);
$check(&mut au_test, &qs_write);
assert!(r == $expect);

View file

@ -50,10 +50,11 @@ where
.flatten()
.collect();
// Sort
// TODO: promote groups to head of the affected_uuids set!
// this could be assisted by indexing in the future by providing a custom compare
// algo!!!
// IDEA: promote groups to head of the affected_uuids set!
//
// This isn't worth doing - it's only used in create/delete, it would not
// really make a large performance difference. Better to target improvements
// in the apply_memberof fn.
affected_uuids.sort();
// Remove dups
affected_uuids.dedup();
@ -63,7 +64,7 @@ where
fn apply_memberof(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
affected_uuids: Vec<&String>,
) -> Result<(), OperationError> {
audit_log!(au, " => entering apply_memberof");
@ -174,7 +175,7 @@ impl Plugin for MemberOf {
fn post_create(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
@ -187,7 +188,7 @@ impl Plugin for MemberOf {
fn post_modify(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
pre_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_me: &ModifyEvent,
@ -198,16 +199,17 @@ impl Plugin for MemberOf {
.zip(cand.iter())
.filter(|(pre, post)| {
// This is the base case to break cycles in recursion!
pre != post
&& (
// AND if it was a group, or will become a group.
(
// If it was a group, or will become a group.
post.attribute_value_pres("class", "group")
|| pre.attribute_value_pres("class", "group")
)
// And the group has changed ...
&& pre != post
// Then memberof should be updated!
})
// Flatten the pre-post tuples. We no longer care if it was
// pre-post
// TODO: Could this be more effecient?
.flat_map(|(pre, post)| vec![pre, post])
.inspect(|e| {
audit_log!(au, "group reporting change: {:?}", e);
@ -229,7 +231,7 @@ impl Plugin for MemberOf {
fn pre_delete(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_de: &DeleteEvent,
) -> Result<(), OperationError> {
@ -256,7 +258,7 @@ impl Plugin for MemberOf {
fn post_delete(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_ce: &DeleteEvent,
) -> Result<(), OperationError> {

View file

@ -19,7 +19,7 @@ trait Plugin {
fn pre_create_transform(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
_cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
@ -32,7 +32,7 @@ trait Plugin {
fn pre_create(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// List of what we will commit that is valid?
_cand: &Vec<Entry<EntryValid, EntryNew>>,
_ce: &CreateEvent,
@ -43,7 +43,7 @@ trait Plugin {
fn post_create(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// List of what we commited that was valid?
_cand: &Vec<Entry<EntryValid, EntryNew>>,
_ce: &CreateEvent,
@ -54,7 +54,7 @@ trait Plugin {
fn pre_modify(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &ModifyEvent,
) -> Result<(), OperationError> {
@ -64,7 +64,7 @@ trait Plugin {
fn post_modify(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// List of what we modified that was valid?
_pre_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
@ -76,7 +76,7 @@ trait Plugin {
fn pre_delete(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_de: &DeleteEvent,
) -> Result<(), OperationError> {
@ -86,7 +86,7 @@ trait Plugin {
fn post_delete(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// List of what we delete that was valid?
_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_ce: &DeleteEvent,
@ -270,7 +270,7 @@ macro_rules! run_verify_plugin {
impl Plugins {
pub fn run_pre_create_transform(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
ce: &CreateEvent,
) -> Result<(), OperationError> {
@ -283,7 +283,7 @@ impl Plugins {
pub fn run_pre_create(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryNew>>,
ce: &CreateEvent,
) -> Result<(), OperationError> {
@ -296,7 +296,7 @@ impl Plugins {
pub fn run_post_create(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryNew>>,
ce: &CreateEvent,
) -> Result<(), OperationError> {
@ -310,7 +310,7 @@ impl Plugins {
pub fn run_pre_modify(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
me: &ModifyEvent,
) -> Result<(), OperationError> {
@ -324,7 +324,7 @@ impl Plugins {
pub fn run_post_modify(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
pre_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
cand: &Vec<Entry<EntryValid, EntryCommitted>>,
me: &ModifyEvent,
@ -342,7 +342,7 @@ impl Plugins {
pub fn run_pre_delete(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
de: &DeleteEvent,
) -> Result<(), OperationError> {
@ -354,7 +354,7 @@ impl Plugins {
pub fn run_post_delete(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryCommitted>>,
de: &DeleteEvent,
) -> Result<(), OperationError> {

View file

@ -19,7 +19,7 @@ impl Plugin for Protected {
fn pre_create(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// List of what we will commit that is valid?
cand: &Vec<Entry<EntryValid, EntryNew>>,
ce: &CreateEvent,
@ -46,7 +46,7 @@ impl Plugin for Protected {
fn pre_modify(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// Should these be EntryValid?
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
me: &ModifyEvent,
@ -118,7 +118,7 @@ impl Plugin for Protected {
fn pre_delete(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_qs: &mut QueryServerWriteTransaction,
// Should these be EntryValid
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
de: &DeleteEvent,

View file

@ -74,7 +74,7 @@ impl Plugin for ReferentialIntegrity {
// be in cand AND db" to simply "is it in the DB?".
fn post_create(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
@ -102,7 +102,7 @@ impl Plugin for ReferentialIntegrity {
fn post_modify(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
_pre_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_cand: &Vec<Entry<EntryValid, EntryCommitted>>,
me: &ModifyEvent,
@ -131,7 +131,7 @@ impl Plugin for ReferentialIntegrity {
fn post_delete(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
qs: &mut QueryServerWriteTransaction,
cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_ce: &DeleteEvent,
) -> Result<(), OperationError> {

View file

@ -143,7 +143,7 @@ impl Handler<CreateRequest> for QueryServerV1 {
fn handle(&mut self, msg: CreateRequest, _: &mut Self::Context) -> Self::Result {
let mut audit = AuditScope::new("create");
let res = audit_segment!(&mut audit, || {
let qs_write = self.qs.write();
let mut qs_write = self.qs.write();
let crt = match CreateEvent::from_request(&mut audit, msg, &qs_write) {
Ok(c) => c,
@ -171,7 +171,7 @@ impl Handler<ModifyRequest> for QueryServerV1 {
fn handle(&mut self, msg: ModifyRequest, _: &mut Self::Context) -> Self::Result {
let mut audit = AuditScope::new("modify");
let res = audit_segment!(&mut audit, || {
let qs_write = self.qs.write();
let mut qs_write = self.qs.write();
let mdf = match ModifyEvent::from_request(&mut audit, msg, &qs_write) {
Ok(m) => m,
Err(e) => {
@ -197,7 +197,7 @@ impl Handler<DeleteRequest> for QueryServerV1 {
fn handle(&mut self, msg: DeleteRequest, _: &mut Self::Context) -> Self::Result {
let mut audit = AuditScope::new("delete");
let res = audit_segment!(&mut audit, || {
let qs_write = self.qs.write();
let mut qs_write = self.qs.write();
let del = match DeleteEvent::from_request(&mut audit, msg, &qs_write) {
Ok(d) => d,
@ -270,7 +270,7 @@ impl Handler<WhoamiMessage> for QueryServerV1 {
// Make an event from the whoami request. This will process the event and
// generate a selfuuid search.
// FIXME: This current handles the unauthenticated check, and will
// TODO: This current handles the unauthenticated check, and will
// trigger the failure, but if we can manage to work out async
// then move this to core.rs, and don't allow Option<UAT> to get
// this far.

View file

@ -68,7 +68,7 @@ pub struct UserAuthToken {
/* ===== low level proto types ===== */
// FIXME: We probably need a proto entry to transform our
// TODO: We probably need a proto entry to transform our
// server core entry into. We also need to get from proto
// entry to our actual entry.
//
@ -265,7 +265,7 @@ pub enum AuthState {
// for the client to view.
Success(UserAuthToken),
// Something was bad, your session is terminated and no cookie.
Denied,
Denied(String),
// Continue to auth, allowed mechanisms listed.
Continue(Vec<AuthAllowed>),
}

View file

@ -123,9 +123,6 @@ pub struct SchemaAttribute {
pub uuid: Uuid,
// Perhaps later add aliases?
pub description: String,
// TODO: Remove this field, it does nothing
// and will be replaced by a different specific schema element.
pub secret: bool,
pub multivalue: bool,
pub index: Vec<IndexType>,
pub syntax: SyntaxType,
@ -144,7 +141,6 @@ impl SchemaAttribute {
}
// uuid
// TODO: Alloc and free of string here?
let uuid = try_audit!(
audit,
Uuid::parse_str(value.get_uuid().as_str())
@ -166,13 +162,6 @@ impl SchemaAttribute {
.ok_or(OperationError::InvalidSchemaState("missing description"))
);
// secret
let secret = try_audit!(
audit,
value
.get_ava_single_bool("secret")
.ok_or(OperationError::InvalidSchemaState("missing secret"))
);
// multivalue
let multivalue = try_audit!(
audit,
@ -201,7 +190,6 @@ impl SchemaAttribute {
name: name.clone(),
uuid: uuid.clone(),
description: description.clone(),
secret: secret,
multivalue: multivalue,
index: index,
syntax: syntax,
@ -272,7 +260,6 @@ impl SchemaAttribute {
}
fn validate_utf8string_insensitive(&self, v: &String) -> Result<(), SchemaError> {
// FIXME: Is there a way to do this that doesn't involve a copy?
let t = v.to_lowercase();
if &t == v {
Ok(())
@ -390,7 +377,7 @@ impl SchemaAttribute {
}
}
// FIXME: This clones everything, which is expensive!
// TODO: This clones everything, which is expensive!
pub fn normalise_value(&self, v: &String) -> String {
match self.syntax {
SyntaxType::SYNTAX_ID => self.normalise_syntax(v),
@ -430,7 +417,6 @@ impl SchemaClass {
}
// uuid
// TODO: Alloc and free of string here?
let uuid = try_audit!(
audit,
Uuid::parse_str(value.get_uuid().as_str())
@ -529,7 +515,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_CLASS)
.expect("unable to parse static uuid"),
description: String::from("The set of classes defining an object"),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -542,7 +527,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_UUID)
.expect("unable to parse static uuid"),
description: String::from("The universal unique id of the object"),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UUID,
@ -555,7 +539,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_NAME)
.expect("unable to parse static uuid"),
description: String::from("The shortform name of an object"),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -567,7 +550,6 @@ impl SchemaInner {
name: String::from("principal_name"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_PRINCIPAL_NAME).expect("unable to parse static uuid"),
description: String::from("The longform name of an object, derived from name and domain. Example: alice@project.org"),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_PRINCIPAL,
@ -580,33 +562,20 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_DESCRIPTION)
.expect("unable to parse static uuid"),
description: String::from("A description of an attribute, object or class"),
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::UTF8STRING,
},
);
s.attributes.insert(String::from("secret"), SchemaAttribute {
// FIXME: Rename from system to schema_private? system_private? attr_private? private_attr?
name: String::from("secret"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_SECRET).expect("unable to parse static uuid"),
description: String::from("If true, this value is always hidden internally to the server, even beyond access controls."),
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::BOOLEAN,
});
s.attributes.insert(String::from("multivalue"), SchemaAttribute {
name: String::from("multivalue"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_MULTIVALUE).expect("unable to parse static uuid"),
description: String::from("If true, this attribute is able to store multiple values rather than just a single value."),
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::BOOLEAN,
});
s.attributes.insert(
// FIXME: Rename to index_attribute? attr_index?
String::from("index"),
SchemaAttribute {
name: String::from("index"),
@ -615,14 +584,12 @@ impl SchemaInner {
description: String::from(
"Describe the indexes to apply to instances of this attribute.",
),
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::INDEX_ID,
},
);
s.attributes.insert(
// FIXME: Rename to attr_syntax?
String::from("syntax"),
SchemaAttribute {
name: String::from("syntax"),
@ -631,14 +598,12 @@ impl SchemaInner {
description: String::from(
"Describe the syntax of this attribute. This affects indexing and sorting.",
),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::SYNTAX_ID,
},
);
s.attributes.insert(
// FIXME: Rename to attribute_systemmay?
String::from("systemmay"),
SchemaAttribute {
name: String::from("systemmay"),
@ -647,14 +612,12 @@ impl SchemaInner {
description: String::from(
"A list of system provided optional attributes this class can store.",
),
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
// FIXME: Rename to attribute_may? schema_may?
String::from("may"),
SchemaAttribute {
name: String::from("may"),
@ -663,14 +626,12 @@ impl SchemaInner {
description: String::from(
"A user modifiable list of optional attributes this class can store.",
),
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
// FIXME: Rename to attribute_systemmust? schema_systemmust?
String::from("systemmust"),
SchemaAttribute {
name: String::from("systemmust"),
@ -679,14 +640,12 @@ impl SchemaInner {
description: String::from(
"A list of system provided required attributes this class must store.",
),
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
// FIXME: Rename to attribute_must? schema_must?
String::from("must"),
SchemaAttribute {
name: String::from("must"),
@ -695,7 +654,6 @@ impl SchemaInner {
description: String::from(
"A user modifiable list of required attributes this class must store.",
),
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -710,7 +668,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ACP_ENABLE)
.expect("unable to parse static uuid"),
description: String::from("A flag to determine if this ACP is active for application. True is enabled, and enforce. False is checked but not enforced."),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::BOOLEAN,
@ -726,7 +683,6 @@ impl SchemaInner {
description: String::from(
"Who the ACP applies to, constraining or allowing operations.",
),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY, IndexType::SUBSTRING],
syntax: SyntaxType::JSON_FILTER,
@ -741,7 +697,6 @@ impl SchemaInner {
description: String::from(
"The effective targets of the ACP, IE what will be acted upon.",
),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY, IndexType::SUBSTRING],
syntax: SyntaxType::JSON_FILTER,
@ -754,7 +709,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ACP_SEARCH_ATTR)
.expect("unable to parse static uuid"),
description: String::from("The attributes that may be viewed or searched by the reciever on targetscope."),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -769,7 +723,6 @@ impl SchemaInner {
description: String::from(
"The set of classes that can be created on a new entry.",
),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -784,7 +737,6 @@ impl SchemaInner {
description: String::from(
"The set of attribute types that can be created on an entry.",
),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -798,7 +750,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVEDATTR)
.expect("unable to parse static uuid"),
description: String::from("The set of attribute types that could be removed or purged in a modification."),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -811,7 +762,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENTATTR)
.expect("unable to parse static uuid"),
description: String::from("The set of attribute types that could be added or asserted in a modification."),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -824,7 +774,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ACP_MODIFY_CLASS)
.expect("unable to parse static uuid"),
description: String::from("The set of class values that could be asserted or added to an entry. Only applies to modify::present operations on class."),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -838,7 +787,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_MEMBEROF)
.expect("unable to parse static uuid"),
description: String::from("reverse group membership of the object"),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::REFERENCE_UUID,
@ -851,7 +799,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_DIRECTMEMBEROF)
.expect("unable to parse static uuid"),
description: String::from("reverse direct group membership of the object"),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::REFERENCE_UUID,
@ -864,7 +811,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_MEMBER)
.expect("unable to parse static uuid"),
description: String::from("List of members of the group"),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::REFERENCE_UUID,
@ -880,7 +826,6 @@ impl SchemaInner {
description: String::from(
"The systems internal migration version for provided objects",
),
secret: true,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -894,7 +839,6 @@ impl SchemaInner {
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_DOMAIN)
.expect("unable to parse static uuid"),
description: String::from("A DNS Domain name entry."),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -913,7 +857,6 @@ impl SchemaInner {
systemmust: vec![
String::from("class"),
String::from("name"),
String::from("secret"),
String::from("multivalue"),
String::from("syntax"),
String::from("description"),
@ -952,11 +895,7 @@ impl SchemaInner {
description: String::from(
"A system created class that all objects must contain",
),
systemmay: vec![
// TODO: Owner? Responsible? Contact?
String::from("description"),
String::from("name"),
],
systemmay: vec![String::from("description"), String::from("name")],
may: vec![],
systemmust: vec![
String::from("class"),
@ -1135,7 +1074,6 @@ impl SchemaInner {
},
);
// TODO: Probably needs to do a collect.
let r = s.validate(&mut au);
if r.len() == 0 {
Ok(s)
@ -1150,7 +1088,7 @@ impl SchemaInner {
pub fn validate(&self, audit: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
let mut res = Vec::new();
// TODO: Does this need to validate anything further at all? The UUID
// Does this need to validate anything further at all? The UUID
// will be checked as part of the schema migration on startup, so I think
// just that all the content is sane is fine.
for class in self.classes.values() {
@ -1262,7 +1200,8 @@ impl<'a> SchemaWriteTransaction<'a> {
// purge all old attributes.
self.inner.attributes.clear();
// Update with new ones.
// TODO: Do we need to check for dups?
// Do we need to check for dups?
// No, they'll over-write each other ... but we do need name uniqueness.
attributetypes.into_iter().for_each(|a| {
self.inner.attributes.insert(a.name.clone(), a);
});
@ -1276,7 +1215,8 @@ impl<'a> SchemaWriteTransaction<'a> {
// purge all old attributes.
self.inner.classes.clear();
// Update with new ones.
// TODO: Do we need to check for dups?
// Do we need to check for dups?
// No, they'll over-write each other ... but we do need name uniqueness.
attributetypes.into_iter().for_each(|a| {
self.inner.classes.insert(a.name.clone(), a);
});
@ -1415,7 +1355,6 @@ mod tests {
"class": ["object", "attributetype"],
"name": ["schema_attr_test"],
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
"secret": ["false"],
"multivalue": ["false"],
"index": ["EQUALITY"],
"syntax": ["UTF8STRING"]
@ -1434,7 +1373,6 @@ mod tests {
"name": ["schema_attr_test"],
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
"description": ["Test attr parsing"],
"secret": ["false"],
"multivalue": ["htouaoeu"],
"index": ["EQUALITY"],
"syntax": ["UTF8STRING"]
@ -1453,7 +1391,6 @@ mod tests {
"name": ["schema_attr_test"],
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
"description": ["Test attr parsing"],
"secret": ["false"],
"multivalue": ["false"],
"index": ["NTEHNOU"],
"syntax": ["UTF8STRING"]
@ -1472,7 +1409,6 @@ mod tests {
"name": ["schema_attr_test"],
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
"description": ["Test attr parsing"],
"secret": ["false"],
"multivalue": ["false"],
"index": ["EQUALITY"],
"syntax": ["TNEOUNTUH"]
@ -1492,7 +1428,6 @@ mod tests {
"name": ["schema_attr_test"],
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
"description": ["Test attr parsing"],
"secret": ["false"],
"multivalue": ["false"],
"syntax": ["UTF8STRING"]
}
@ -1511,7 +1446,6 @@ mod tests {
"name": ["schema_attr_test"],
"uuid": ["66c68b2f-d02c-4243-8013-7946e40fe321"],
"description": ["Test attr parsing"],
"secret": ["false"],
"multivalue": ["false"],
"index": ["EQUALITY"],
"syntax": ["UTF8STRING"]
@ -1683,7 +1617,6 @@ mod tests {
name: String::from("principal_name"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_PRINCIPAL_NAME).expect("unable to parse static uuid"),
description: String::from("The longform name of an object, derived from name and domain. Example: alice@project.org"),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_PRINCIPAL,
@ -1714,7 +1647,6 @@ mod tests {
description: String::from(
"Who the ACP applies to, constraining or allowing operations.",
),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY, IndexType::SUBSTRING],
syntax: SyntaxType::JSON_FILTER,
@ -1748,7 +1680,6 @@ mod tests {
name: String::from("uuid"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_UUID).expect("unable to parse static uuid"),
description: String::from("The universal unique id of the object"),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UUID,
@ -1769,7 +1700,6 @@ mod tests {
name: String::from("single_value"),
uuid: Uuid::new_v4(),
description: String::from(""),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
@ -1789,7 +1719,6 @@ mod tests {
name: String::from("mv_string"),
uuid: Uuid::new_v4(),
description: String::from(""),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING,
@ -1804,7 +1733,6 @@ mod tests {
name: String::from("mv_bool"),
uuid: Uuid::new_v4(),
description: String::from(""),
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::BOOLEAN,
@ -1824,7 +1752,6 @@ mod tests {
name: String::from("sv_syntax"),
uuid: Uuid::new_v4(),
description: String::from(""),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::SYNTAX_ID,
@ -1841,7 +1768,6 @@ mod tests {
name: String::from("sv_index"),
uuid: Uuid::new_v4(),
description: String::from(""),
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::INDEX_ID,
@ -1951,7 +1877,6 @@ mod tests {
"class": ["object", "attributetype"],
"name": ["testattr"],
"description": ["testattr"],
"secret": ["false"],
"multivalue": ["false"],
"syntax": ["UTF8STRING"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
@ -1974,7 +1899,6 @@ mod tests {
"class": ["object", "attributetype"],
"name": ["testattr"],
"description": ["testattr"],
"secret": ["false"],
"multivalue": ["zzzzz"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"syntax": ["UTF8STRING"]
@ -1996,7 +1920,6 @@ mod tests {
"class": ["object", "attributetype"],
"name": ["testattr"],
"description": ["testattr"],
"secret": ["false"],
"multivalue": ["true"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"syntax": ["UTF8STRING"]
@ -2118,7 +2041,7 @@ mod tests {
"attrs": {
"class": ["extensibleobject"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"secret": ["zzzz"]
"multivalue": ["zzzz"]
}
}"#,
)
@ -2136,7 +2059,7 @@ mod tests {
"attrs": {
"class": ["extensibleobject"],
"uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"],
"secret": ["true"]
"multivalue": ["true"]
}
}"#,
)
@ -2160,7 +2083,7 @@ mod tests {
);
// test syntax of bool
let f_bool = filter_all!(f_eq("secret", "zzzz"));
let f_bool = filter_all!(f_eq("multivalue", "zzzz"));
assert_eq!(
f_bool.validate(&schema),
Err(SchemaError::InvalidAttributeSyntax)
@ -2174,14 +2097,14 @@ mod tests {
// Test the recursive structures validate
let f_or_empty = filter_all!(f_or!([]));
assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
let f_or = filter_all!(f_or!([f_eq("secret", "zzzz")]));
let f_or = filter_all!(f_or!([f_eq("multivalue", "zzzz")]));
assert_eq!(
f_or.validate(&schema),
Err(SchemaError::InvalidAttributeSyntax)
);
let f_or_mult = filter_all!(f_and!([
f_eq("class", "attributetype"),
f_eq("secret", "zzzzzzz"),
f_eq("multivalue", "zzzzzzz"),
]));
assert_eq!(
f_or_mult.validate(&schema),

View file

@ -16,9 +16,11 @@ use crate::constants::{
JSON_IDM_ADMINS_V1, JSON_IDM_SELF_ACP_READ_V1, JSON_SCHEMA_ATTR_DISPLAYNAME,
JSON_SCHEMA_ATTR_MAIL, JSON_SCHEMA_ATTR_PASSWORD, JSON_SCHEMA_ATTR_SSH_PUBLICKEY,
JSON_SCHEMA_CLASS_ACCOUNT, JSON_SCHEMA_CLASS_GROUP, JSON_SCHEMA_CLASS_PERSON,
JSON_SYSTEM_INFO_V1,
JSON_SYSTEM_INFO_V1, UUID_DOES_NOT_EXIST,
};
use crate::entry::{
Entry, EntryCommitted, EntryInvalid, EntryNew, EntryNormalised, EntryReduced, EntryValid,
};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryNormalised, EntryValid};
use crate::error::{ConsistencyError, OperationError, SchemaError};
use crate::event::{
CreateEvent, DeleteEvent, Event, EventOrigin, ExistsEvent, ModifyEvent, ReviveRecycledEvent,
@ -49,7 +51,7 @@ pub trait QueryServerTransaction {
&self,
au: &mut AuditScope,
se: &SearchEvent,
) -> Result<Vec<Entry<EntryValid, EntryCommitted>>, OperationError> {
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
/*
* This just wraps search, but it's for the external interface
* so as a result it also reduces the entry set's attributes at
@ -81,34 +83,15 @@ pub trait QueryServerTransaction {
// exists in the server, not arbitrary attr names.
//
// This normalises and validates in a single step.
/*
let vf = match se.filter.validate(self.get_schema()) {
Ok(f) => f,
// TODO: Do something with this error
Err(e) => return Err(OperationError::SchemaViolation(e)),
};
audit_log!(au, "search: valid filter -> {:?}", vf);
*/
//
// NOTE: Filters are validated in event conversion.
// Now resolve all references.
let vfr = try_audit!(au, se.filter.resolve(&se.event));
// TODO: Assert access control allows the filter and requested attrs.
/*
let mut audit_plugin_pre = AuditScope::new("plugin_pre_search");
let plug_pre_res = Plugins::run_pre_search(&mut audit_plugin_pre);
au.append_scope(audit_plugin_pre);
match plug_pre_res {
Ok(_) => {}
Err(e) => {
audit_log!(au, "Search operation failed (plugin), {:?}", e);
return Err(e);
}
}
*/
// NOTE: We currently can't build search plugins due to the inability to hand
// the QS wr/ro to the plugin trait. However, there shouldn't be a need for search
// plugis, because all data transforms should be in the write path.
let mut audit_be = AuditScope::new("backend_search");
let res = self
@ -125,8 +108,6 @@ pub trait QueryServerTransaction {
// ACP application. There is a second application to reduce the
// attribute set on the entries!
//
// TODO: Make a search_ext that applies search_filter_entry_attributes
// and does Entry -> EntryReduced.
let mut audit_acp = AuditScope::new("access_control_profiles");
let access = self.get_accesscontrols();
let acp_res = access.search_filter_entries(&mut audit_acp, se, res);
@ -134,34 +115,12 @@ pub trait QueryServerTransaction {
au.append_scope(audit_acp);
let acp_res = try_audit!(au, acp_res);
/*
let mut audit_plugin_post = AuditScope::new("plugin_post_search");
let plug_post_res = Plugins::run_post_search(&mut audit_plugin_post);
au.append_scope(audit_plugin_post);
match plug_post_res {
Ok(_) => {}
Err(e) => {
audit_log!(au, "Search operation failed (plugin), {:?}", e);
return Err(e);
}
}
*/
Ok(acp_res)
}
fn exists(&self, au: &mut AuditScope, ee: &ExistsEvent) -> Result<bool, OperationError> {
let mut audit_be = AuditScope::new("backend_exists");
// How to get schema?
/*
let vf = match ee.filter.validate(self.get_schema()) {
Ok(f) => f,
Err(e) => return Err(OperationError::SchemaViolation(e)),
};
*/
let vfr = try_audit!(au, ee.filter.resolve(&ee.event));
let res = self
@ -173,7 +132,7 @@ pub trait QueryServerTransaction {
res
}
// TODO: Should this actually be names_to_uuids and we do batches?
// Should this actually be names_to_uuids and we do batches?
// In the initial design "no", we can always write a batched
// interface later.
//
@ -218,7 +177,7 @@ pub trait QueryServerTransaction {
return Err(OperationError::InvalidDBState);
}
// TODO: fine for 0/1 case, but check len for >= 2 to eliminate that case.
// error should never be triggered due to the len checks above.
let e = res.first().ok_or(OperationError::NoMatchingEntries)?;
// Get the uuid from the entry. Again, check it exists, and only one.
let uuid_res: String = e.get_uuid().to_string();
@ -380,13 +339,13 @@ pub trait QueryServerTransaction {
value: &String,
) -> Result<String, OperationError> {
let schema = self.get_schema();
// TODO: Normalise the attr, else lookup with fail ....
let schema_name = schema
.get_attributes()
.get("name")
.expect("Schema corrupted");
// TODO: Should we return the normalise attr?
// Should we return the normalise attr?
// no, I think that it's not up to us to normalise this all the time.
let temp_a = schema_name.normalise_value(attr);
// Lookup the attr
@ -399,31 +358,19 @@ pub trait QueryServerTransaction {
// So, if possible, resolve the value
// to a concrete uuid.
Ok(_) => {
// TODO: Should this check existance?
// Could this be a security risk for disclosure?
// So it would only reveal if a name/uuid did/did not exist
// because this pre-acp check, but inversely, this means if we
// fail fast here, we would not hae a situation where we would create
// then ref-int would invalidate the structure immediately.
//
// I can see a situation where you would modify, and then immediately
// have the mod removed because it would fail the refint (IE add
// raw uuid X, then immediately it's removed)
//
// This would never be the case with resolved uuid's though, because
// they are inside the txn. So do we just ignore this as an edge case?
//
// For now, refint will fight the raw uuid's, and will be tested to
// assume they don't exist on create/mod/etc.. If this check was added
// then refint may not need post_create handlers.
// It's a uuid - we do NOT check for existance, because that
// could be revealing or disclosing - it is up to acp to assert
// if we can see the value or not, and it's not up to us to
// assert the filter value exists.
Ok(value.clone())
}
Err(_) => {
// it's not a uuid, try to resolve it.
// TODO: If this errors, should we actually pass
// back a place holder "no such uuid" and then
// fail an exists check later?
Ok(self.name_to_uuid(audit, value)?)
// if the value is NOT found, we map to "does not exist" to allow
// the value to continue being evaluated, which of course, will fail
// all subsequent filter tests because it ... well, doesn't exist.
self.name_to_uuid(audit, value)
.or_else(|_| Ok(UUID_DOES_NOT_EXIST.to_string()))
}
}
}
@ -434,9 +381,9 @@ pub trait QueryServerTransaction {
}
}
None => {
// Welp, you'll break when we hit schema validation soon anyway.
// Just clone in this case ...
// TODO: Honestly, we could just return the schema error here ...
// No attribute of this name exists - clone the value anyway, because
// the schema check will fail soon, and it's beyond this functions
// purpose to validate your content anyway.
Ok(value.clone())
}
}
@ -530,12 +477,14 @@ impl QueryServerReadTransaction {
pub struct QueryServerWriteTransaction<'a> {
committed: bool,
// be_write_txn: BackendWriteTransaction,
// schema_write: SchemaWriteTransaction,
// read: QueryServerReadTransaction,
be_txn: BackendWriteTransaction,
schema: SchemaWriteTransaction<'a>,
accesscontrols: AccessControlsWriteTransaction<'a>,
// We store a set of flags that indicate we need a reload of
// schema or acp, which is tested by checking the classes of the
// changing content.
changed_schema: bool,
changed_acp: bool,
}
impl<'a> QueryServerTransaction for QueryServerWriteTransaction<'a> {
@ -596,21 +545,23 @@ impl QueryServer {
be_txn: self.be.write(),
schema: self.schema.write(),
accesscontrols: self.accesscontrols.write(),
changed_schema: false,
changed_acp: false,
}
}
pub(crate) fn initialise_helper(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
let ts_write_1 = self.write();
let mut ts_write_1 = self.write();
ts_write_1
.initialise_schema_core(audit)
.and_then(|_| ts_write_1.commit(audit))?;
let ts_write_2 = self.write();
let mut ts_write_2 = self.write();
ts_write_2
.initialise_schema_idm(audit)
.and_then(|_| ts_write_2.commit(audit))?;
let ts_write_3 = self.write();
let mut ts_write_3 = self.write();
ts_write_3
.initialise_idm(audit)
.and_then(|_| ts_write_3.commit(audit))
@ -623,7 +574,7 @@ impl QueryServer {
}
impl<'a> QueryServerWriteTransaction<'a> {
pub fn create(&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.
@ -637,7 +588,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
let candidates: Vec<Entry<EntryInvalid, EntryNew>> =
ce.entries.iter().map(|er| er.clone()).collect();
// TODO: Normalise but DO NOT validate the entries.
// Normalise but DO NOT validate the entries.
let norm_cand: Result<Vec<Entry<EntryNormalised, EntryNew>>, _> = candidates
.into_iter()
.map(|e| {
@ -671,7 +622,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
let mut audit_plugin_pre_transform = AuditScope::new("plugin_pre_create_transform");
let plug_pre_transform_res = Plugins::run_pre_create_transform(
&mut audit_plugin_pre_transform,
&self,
self,
&mut candidates,
ce,
);
@ -702,7 +653,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// This is important for normalisation of certain types IE class
// or attributes for these checks.
let mut audit_plugin_pre = AuditScope::new("plugin_pre_create");
let plug_pre_res = Plugins::run_pre_create(&mut audit_plugin_pre, &self, &norm_cand, ce);
let plug_pre_res = Plugins::run_pre_create(&mut audit_plugin_pre, self, &norm_cand, ce);
au.append_scope(audit_plugin_pre);
let _ = try_audit!(au, plug_pre_res, "Create operation failed (plugin), {:?}");
@ -725,7 +676,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Run any post plugins
let mut audit_plugin_post = AuditScope::new("plugin_post_create");
let plug_post_res = Plugins::run_post_create(&mut audit_plugin_post, &self, &norm_cand, ce);
let plug_post_res = Plugins::run_post_create(&mut audit_plugin_post, self, &norm_cand, ce);
au.append_scope(audit_plugin_post);
if plug_post_res.is_err() {
@ -733,13 +684,37 @@ impl<'a> QueryServerWriteTransaction<'a> {
return plug_post_res;
}
// We have finished all plugs and now have a successful operation - flag if
// schema or acp requires reload.
self.changed_schema = norm_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", "classtype")
|| e.attribute_value_pres("class", "attributetype")
}
});
self.changed_acp = norm_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", "access_control_profile")
}
});
audit_log!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
// We are complete, finalise logging and return
audit_log!(au, "Create operation success");
res
}
pub fn delete(&self, au: &mut AuditScope, de: &DeleteEvent) -> Result<(), OperationError> {
pub fn delete(&mut self, au: &mut AuditScope, de: &DeleteEvent) -> Result<(), OperationError> {
// Do you have access to view all the set members? Reduce based on your
// read permissions and attrs
// THIS IS PRETTY COMPLEX SEE THE DESIGN DOC
@ -801,7 +776,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Pre delete plugs
let mut audit_plugin_pre = AuditScope::new("plugin_pre_delete");
let plug_pre_res =
Plugins::run_pre_delete(&mut audit_plugin_pre, &self, &mut candidates, de);
Plugins::run_pre_delete(&mut audit_plugin_pre, self, &mut candidates, de);
au.append_scope(audit_plugin_pre);
if plug_pre_res.is_err() {
@ -832,7 +807,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Post delete plugs
let mut audit_plugin_post = AuditScope::new("plugin_post_delete");
let plug_post_res = Plugins::run_post_delete(&mut audit_plugin_post, &self, &del_cand, de);
let plug_post_res = Plugins::run_post_delete(&mut audit_plugin_post, self, &del_cand, de);
au.append_scope(audit_plugin_post);
if plug_post_res.is_err() {
@ -840,6 +815,30 @@ impl<'a> QueryServerWriteTransaction<'a> {
return plug_post_res;
}
// We have finished all plugs and now have a successful operation - flag if
// schema or acp requires reload.
self.changed_schema = del_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", "classtype")
|| e.attribute_value_pres("class", "attributetype")
}
});
self.changed_acp = del_cand.iter().fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", "access_control_profile")
}
});
audit_log!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
// Send result
audit_log!(au, "Delete operation success");
res
@ -906,7 +905,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Should this take a revive event?
pub fn revive_recycled(
&self,
&mut self,
au: &mut AuditScope,
re: &ReviveRecycledEvent,
) -> Result<(), OperationError> {
@ -934,7 +933,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.impersonate_modify_valid(au, re.filter.clone(), re.filter.clone(), m_valid, &re.event)
}
pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> {
pub fn modify(&mut 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.
@ -1021,7 +1020,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Pre mod plugins
let mut audit_plugin_pre = AuditScope::new("plugin_pre_modify");
let plug_pre_res =
Plugins::run_pre_modify(&mut audit_plugin_pre, &self, &mut candidates, me);
Plugins::run_pre_modify(&mut audit_plugin_pre, self, &mut candidates, me);
au.append_scope(audit_plugin_pre);
if plug_pre_res.is_err() {
@ -1029,7 +1028,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
return plug_pre_res;
}
// TODO: There is a potential optimisation here, where if
// NOTE: There is a potential optimisation here, where if
// candidates == pre-candidates, then we don't need to store anything
// because we effectively just did an assert. However, like all
// optimisations, this could be premature - so we for now, just
@ -1065,7 +1064,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
let mut audit_plugin_post = AuditScope::new("plugin_post_modify");
let plug_post_res = Plugins::run_post_modify(
&mut audit_plugin_post,
&self,
self,
&pre_candidates,
&norm_cand,
me,
@ -1077,6 +1076,38 @@ impl<'a> QueryServerWriteTransaction<'a> {
return plug_post_res;
}
// We have finished all plugs and now have a successful operation - flag if
// schema or acp requires reload. Remember, this is a modify, so we need to check
// pre and post cands.
self.changed_schema =
norm_cand
.iter()
.chain(pre_candidates.iter())
.fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", "classtype")
|| e.attribute_value_pres("class", "attributetype")
}
});
self.changed_acp = norm_cand
.iter()
.chain(pre_candidates.iter())
.fold(false, |acc, e| {
if acc {
acc
} else {
e.attribute_value_pres("class", "access_control_profile")
}
});
audit_log!(
au,
"Schema reload: {:?}, ACP reload: {:?}",
self.changed_schema,
self.changed_acp
);
// return
audit_log!(au, "Modify operation success");
res
@ -1087,7 +1118,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// only, allowing certain plugin by passes etc.
pub fn internal_create(
&self,
&mut self,
audit: &mut AuditScope,
entries: Vec<Entry<EntryInvalid, EntryNew>>,
) -> Result<(), OperationError> {
@ -1101,7 +1132,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn internal_delete(
&self,
&mut self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
) -> Result<(), OperationError> {
@ -1116,7 +1147,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn internal_modify(
&self,
&mut self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
@ -1135,7 +1166,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn impersonate_modify_valid(
&self,
&mut self,
audit: &mut AuditScope,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
@ -1150,7 +1181,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn impersonate_modify(
&self,
&mut self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
filter_intent: Filter<FilterInvalid>,
@ -1185,7 +1216,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn internal_migrate_or_create_str(
&self,
&mut self,
audit: &mut AuditScope,
e_str: &str,
) -> Result<(), OperationError> {
@ -1200,7 +1231,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn internal_migrate_or_create(
&self,
&mut self,
audit: &mut AuditScope,
e: Entry<EntryValid, EntryNew>,
) -> Result<(), OperationError> {
@ -1209,11 +1240,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
// attributes in the situation.
// If not exist, create from Entry B
//
// TODO: WARNING: this requires schema awareness for multivalue types!
// We need to either do a schema aware merge, or we just overwrite those
// few attributes.
//
// This will extra classes an attributes alone!
//
// NOTE: gen modlist IS schema aware and will handle multivalue
// correctly!
let filt = match e.filter_from_attrs(&vec![String::from("uuid")]) {
Some(f) => f,
None => return Err(OperationError::FilterGeneration),
@ -1250,7 +1280,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Should this take a be_txn?
pub fn internal_assert_or_create(
&self,
&mut self,
audit: &mut AuditScope,
e: Entry<EntryValid, EntryNew>,
) -> Result<(), OperationError> {
@ -1264,7 +1294,9 @@ impl<'a> QueryServerWriteTransaction<'a> {
None => return Err(OperationError::FilterGeneration),
};
// Does it exist? (TODO: Should be search, not exists ...)
// Does it exist? we use search here, not exists, so that if the entry does exist
// we can compare it is identical, which avoids a delete/create cycle that would
// trigger csn/repl each time we start up.
match self.internal_search(audit, filt.clone()) {
Ok(results) => {
if results.len() == 0 {
@ -1290,7 +1322,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
}
pub fn initialise_schema_core(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
pub fn initialise_schema_core(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
// Load in all the "core" schema, that we already have in "memory".
let entries = self.schema.to_entries();
@ -1310,7 +1342,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
r
}
pub fn initialise_schema_idm(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
pub fn initialise_schema_idm(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
// List of IDM schemas to init.
let idm_schema: Vec<&str> = vec![
JSON_SCHEMA_ATTR_DISPLAYNAME,
@ -1325,17 +1357,17 @@ impl<'a> QueryServerWriteTransaction<'a> {
let mut audit_si = AuditScope::new("start_initialise_schema_idm");
let r: Result<Vec<()>, _> = idm_schema
.iter()
// Each item individually logs it's result
.map(|e_str| self.internal_migrate_or_create_str(&mut audit_si, e_str))
.collect();
audit.append_scope(audit_si);
assert!(r.is_ok());
// TODO: Should we log the set of failures some how?
r.map(|_| ())
}
// This function is idempotent
pub fn initialise_idm(&self, audit: &mut AuditScope) -> Result<(), OperationError> {
pub fn initialise_idm(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
// First, check the system_info object. This stores some server information
// and details. It's a pretty static thing.
let mut audit_si = AuditScope::new("start_system_info");
@ -1512,16 +1544,20 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
pub fn commit(mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
// TODO: This could be faster if we cache the set of classes changed
// This could be faster if we cache the set of classes changed
// in an operation so we can check if we need to do the reload or not
//
// Reload the schema from qs.
self.reload_schema(audit)?;
if self.changed_schema {
self.reload_schema(audit)?;
}
// Determine if we need to update access control profiles
// based on any modifications that have occured.
// IF SCHEMA CHANGED WE MUST ALSO RELOAD!!! IE if schema had an attr removed
// that we rely on we MUST fail this!
self.reload_accesscontrols(audit)?;
// that we rely on we MUST fail this here!!
if self.changed_schema || self.changed_acp {
self.reload_accesscontrols(audit)?;
}
// Now destructure the transaction ready to reset it.
let QueryServerWriteTransaction {
@ -1529,6 +1565,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
be_txn,
schema,
accesscontrols,
changed_schema,
changed_acp,
} = self;
assert!(!committed);
// Begin an audit.
@ -1536,12 +1574,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
let r = schema.validate(audit);
if r.len() == 0 {
// TODO: At this point, if validate passes, we probably actually want
// to perform a schema reload BEFORE we be commit. Because the be holds
// all the data, we need everything to be consistent *first* as the be
// is the last point we can really backout!
// Alternate, we attempt to reload during batch ops, but this seems
// costly.
// Schema has been validated, so we can go ahead and commit it with the be
// because both are consistent.
schema
.commit()
.and_then(|_| accesscontrols.commit().and_then(|_| be_txn.commit()))
@ -1570,7 +1604,7 @@ mod tests {
#[test]
fn test_qs_create_user() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let filt = filter!(f_eq("name", "testperson"));
let admin = server_txn
.internal_search_uuid(audit, UUID_ADMIN)
@ -1619,23 +1653,23 @@ mod tests {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
{
// Setup and abort.
let server_txn = server.write();
let mut server_txn = server.write();
assert!(server_txn.initialise_schema_core(audit).is_ok());
}
{
let server_txn = server.write();
let mut server_txn = server.write();
assert!(server_txn.initialise_schema_core(audit).is_ok());
assert!(server_txn.initialise_schema_core(audit).is_ok());
assert!(server_txn.commit(audit).is_ok());
}
{
// Now do it again in a new txn, but abort
let server_txn = server.write();
let mut server_txn = server.write();
assert!(server_txn.initialise_schema_core(audit).is_ok());
}
{
// Now do it again in a new txn.
let server_txn = server.write();
let mut server_txn = server.write();
assert!(server_txn.initialise_schema_core(audit).is_ok());
assert!(server_txn.commit(audit).is_ok());
}
@ -1647,7 +1681,7 @@ mod tests {
fn test_qs_modify() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
// Create an object
let server_txn = server.write();
let mut server_txn = server.write();
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
@ -1778,7 +1812,7 @@ mod tests {
// Test modifying an entry and adding an extra class, that would cause the entry
// to no longer conform to schema.
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
@ -1855,7 +1889,7 @@ mod tests {
fn test_qs_delete() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
// Create
let server_txn = server.write();
let mut server_txn = server.write();
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
@ -1939,7 +1973,7 @@ mod tests {
#[test]
fn test_qs_tombstone() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let admin = server_txn
.internal_search_uuid(audit, UUID_ADMIN)
.expect("failed");
@ -2024,7 +2058,7 @@ mod tests {
#[test]
fn test_qs_recycle_simple() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let admin = server_txn
.internal_search_uuid(audit, UUID_ADMIN)
.expect("failed");
@ -2165,7 +2199,7 @@ mod tests {
fn test_qs_recycle_advanced() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
// Create items
let server_txn = server.write();
let mut server_txn = server.write();
let admin = server_txn
.internal_search_uuid(audit, UUID_ADMIN)
.expect("failed");
@ -2210,7 +2244,7 @@ mod tests {
#[test]
fn test_qs_name_to_uuid() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
@ -2248,7 +2282,7 @@ mod tests {
#[test]
fn test_qs_uuid_to_name() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
@ -2289,7 +2323,7 @@ mod tests {
#[test]
fn test_qs_clone_value() {
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let server_txn = server.write();
let mut server_txn = server.write();
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
"valid": null,
@ -2369,7 +2403,7 @@ mod tests {
)
.expect("json failure");
let server_txn = server.write();
let mut server_txn = server.write();
// Add a new class.
let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
assert!(server_txn.create(audit, &ce_class).is_ok());
@ -2381,7 +2415,7 @@ mod tests {
server_txn.commit(audit).expect("should not fail");
// Start a new write
let server_txn = server.write();
let mut server_txn = server.write();
// Add the class to an object
// should work
let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
@ -2391,7 +2425,7 @@ mod tests {
server_txn.commit(audit).expect("should not fail");
// Start a new write
let server_txn = server.write();
let mut server_txn = server.write();
// delete the class
let de_class =
unsafe { DeleteEvent::new_internal_invalid(filter!(f_eq("name", "testclass"))) };
@ -2400,7 +2434,7 @@ mod tests {
server_txn.commit(audit).expect("should not fail");
// Start a new write
let server_txn = server.write();
let mut server_txn = server.write();
// Trying to add now should fail
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_fail).is_err());
@ -2444,14 +2478,13 @@ mod tests {
"uuid": ["cfcae205-31c3-484b-8ced-667d1709c5e3"],
"description": ["Test Attribute"],
"multivalue": ["false"],
"secret": ["false"],
"syntax": ["UTF8STRING"]
}
}"#,
)
.expect("json failure");
let server_txn = server.write();
let mut server_txn = server.write();
// Add a new attribute.
let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
assert!(server_txn.create(audit, &ce_attr).is_ok());
@ -2463,7 +2496,7 @@ mod tests {
server_txn.commit(audit).expect("should not fail");
// Start a new write
let server_txn = server.write();
let mut server_txn = server.write();
// Add the attr to an object
// should work
let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
@ -2473,7 +2506,7 @@ mod tests {
server_txn.commit(audit).expect("should not fail");
// Start a new write
let server_txn = server.write();
let mut server_txn = server.write();
// delete the attr
let de_attr =
unsafe { DeleteEvent::new_internal_invalid(filter!(f_eq("name", "testattr"))) };
@ -2482,7 +2515,7 @@ mod tests {
server_txn.commit(audit).expect("should not fail");
// Start a new write
let server_txn = server.write();
let mut server_txn = server.write();
// Trying to add now should fail
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(audit, &ce_fail).is_err());