diff --git a/src/lib/be/mod.rs b/src/lib/be/mod.rs index 6b1cfab1e..99443d9a6 100644 --- a/src/lib/be/mod.rs +++ b/src/lib/be/mod.rs @@ -806,6 +806,10 @@ mod tests { let mut e: Entry = Entry::new(); e.add_ava(String::from("userid"), String::from("william")); + e.add_ava( + "uuid".to_string(), + "db237e8a-0079-4b8c-8a56-593b22aa44d1".to_string(), + ); let e = unsafe { e.to_valid_new() }; let single_result = be.create(audit, &vec![e.clone()]); @@ -824,6 +828,10 @@ mod tests { let mut e: Entry = Entry::new(); e.add_ava(String::from("userid"), String::from("claire")); + e.add_ava( + "uuid".to_string(), + "db237e8a-0079-4b8c-8a56-593b22aa44d1".to_string(), + ); let e = unsafe { e.to_valid_new() }; let single_result = be.create(audit, &vec![e.clone()]); @@ -850,9 +858,17 @@ mod tests { // First create some entries (3?) let mut e1: Entry = Entry::new(); e1.add_ava(String::from("userid"), String::from("william")); + e1.add_ava( + "uuid".to_string(), + "db237e8a-0079-4b8c-8a56-593b22aa44d1".to_string(), + ); let mut e2: Entry = Entry::new(); e2.add_ava(String::from("userid"), String::from("alice")); + e2.add_ava( + "uuid".to_string(), + "4b6228ab-1dbe-42a4-a9f5-f6368222438e".to_string(), + ); let ve1 = unsafe { e1.clone().to_valid_new() }; let ve2 = unsafe { e2.clone().to_valid_new() }; @@ -912,12 +928,24 @@ mod tests { // First create some entries (3?) let mut e1: Entry = Entry::new(); e1.add_ava(String::from("userid"), String::from("william")); + e1.add_ava( + "uuid".to_string(), + "db237e8a-0079-4b8c-8a56-593b22aa44d1".to_string(), + ); let mut e2: Entry = Entry::new(); e2.add_ava(String::from("userid"), String::from("alice")); + e2.add_ava( + "uuid".to_string(), + "4b6228ab-1dbe-42a4-a9f5-f6368222438e".to_string(), + ); let mut e3: Entry = Entry::new(); e3.add_ava(String::from("userid"), String::from("lucy")); + e3.add_ava( + "uuid".to_string(), + "7b23c99d-c06b-4a9a-a958-3afa56383e1d".to_string(), + ); let ve1 = unsafe { e1.clone().to_valid_new() }; let ve2 = unsafe { e2.clone().to_valid_new() }; @@ -950,6 +978,10 @@ mod tests { // the state machine rules here!!!! let mut e4: Entry = Entry::new(); e4.add_ava(String::from("userid"), String::from("amy")); + e4.add_ava( + "uuid".to_string(), + "21d816b5-1f6a-4696-b7c1-6ed06d22ed81".to_string(), + ); let ve4 = unsafe { e4.clone().to_valid_committed() }; @@ -978,12 +1010,24 @@ mod tests { // First create some entries (3?) let mut e1: Entry = Entry::new(); e1.add_ava(String::from("userid"), String::from("william")); + e1.add_ava( + "uuid".to_string(), + "db237e8a-0079-4b8c-8a56-593b22aa44d1".to_string(), + ); let mut e2: Entry = Entry::new(); e2.add_ava(String::from("userid"), String::from("alice")); + e2.add_ava( + "uuid".to_string(), + "4b6228ab-1dbe-42a4-a9f5-f6368222438e".to_string(), + ); let mut e3: Entry = Entry::new(); e3.add_ava(String::from("userid"), String::from("lucy")); + e3.add_ava( + "uuid".to_string(), + "7b23c99d-c06b-4a9a-a958-3afa56383e1d".to_string(), + ); let ve1 = unsafe { e1.clone().to_valid_new() }; let ve2 = unsafe { e2.clone().to_valid_new() }; diff --git a/src/lib/constants.rs b/src/lib/constants.rs index dcbf73fb5..fa75336d1 100644 --- a/src/lib/constants.rs +++ b/src/lib/constants.rs @@ -1,10 +1,12 @@ pub static PURGE_TIMEOUT: u64 = 3600; -pub static _UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000"; +pub static UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000"; pub static _UUID_ANONYMOUS: &'static str = "00000000-0000-0000-0000-ffffffffffff"; pub static JSON_ANONYMOUS_V1: &'static str = r#"{ - "valid": null, + "valid": { + "uuid": "00000000-0000-0000-0000-ffffffffffff" + }, "state": null, "attrs": { "class": ["account", "object"], @@ -18,7 +20,9 @@ pub static JSON_ANONYMOUS_V1: &'static str = r#"{ pub static _UUID_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffffff000001"; pub static JSON_SYSTEM_INFO_V1: &'static str = r#"{ - "valid": null, + "valid": { + "uuid": "00000000-0000-0000-0000-ffffff000001" + }, "state": null, "attrs": { "class": ["object", "system_info"], diff --git a/src/lib/entry.rs b/src/lib/entry.rs index 51f3e9f22..26e78744b 100644 --- a/src/lib/entry.rs +++ b/src/lib/entry.rs @@ -131,10 +131,16 @@ pub struct EntryCommitted { } // It's been in the DB, so it has an id // pub struct EntryPurged; +#[derive(Clone, Debug, Deserialize)] +pub struct EntryValid { + // Asserted with schema, so we know it has a UUID now ... + uuid: String, +} + +// Modified, can't be sure of it's content! We therefore disregard the UUID +// and on validate, we check it again. #[derive(Clone, Copy, Debug, Deserialize)] -pub struct EntryValid; // Asserted with schema. -#[derive(Clone, Copy, Debug, Deserialize)] -pub struct EntryInvalid; // Modified +pub struct EntryInvalid; #[derive(Debug, Deserialize)] pub struct Entry { @@ -205,6 +211,13 @@ impl Entry { } impl Entry { + fn get_uuid(&self) -> Option<&String> { + match self.attrs.get("uuid") { + Some(vs) => vs.first(), + None => None, + } + } + pub fn validate( self, schema: &SchemaReadTransaction, @@ -230,7 +243,7 @@ impl Entry { let mut new_attrs = BTreeMap::new(); - // First normalise + // First normalise - this checks and fixes our UUID. for (attr_name, avas) in attrs.iter() { let attr_name_normal: String = schema_attr_name.normalise_value(attr_name); // Get the needed schema type @@ -255,8 +268,16 @@ impl Entry { let _ = new_attrs.insert(attr_name_normal, avas_normal); } + let uuid: String = match new_attrs.get("uuid") { + Some(vs) => match vs.first() { + Some(uuid) => uuid.to_string(), + None => return Err(SchemaError::MissingMustAttribute("uuid".to_string())), + }, + None => return Err(SchemaError::MissingMustAttribute("uuid".to_string())), + }; + let ne = Entry { - valid: EntryValid, + valid: EntryValid { uuid }, state: state, attrs: new_attrs, }; @@ -359,13 +380,13 @@ impl Entry { impl Clone for Entry where - VALID: Copy, + VALID: Clone, STATE: Copy, { // Dirty modifiable state. Works on any other state to dirty them. fn clone(&self) -> Entry { Entry { - valid: self.valid, + valid: self.valid.clone(), state: self.state, attrs: self.attrs.clone(), } @@ -376,11 +397,13 @@ where * A series of unsafe transitions allowing entries to skip certain steps in * the process to facilitate eq/checks. */ -impl Entry { +impl Entry { #[cfg(test)] pub unsafe fn to_valid_new(self) -> Entry { Entry { - valid: EntryValid, + valid: EntryValid { + uuid: self.get_uuid().expect("Invalid uuid").to_string(), + }, state: EntryNew, attrs: self.attrs, } @@ -388,22 +411,37 @@ impl Entry { } // Both invalid states can be reached from "entry -> invalidate" -impl Entry { +impl Entry { + #[cfg(test)] + pub unsafe fn to_valid_new(self) -> Entry { + Entry { + valid: EntryValid { + uuid: self.get_uuid().expect("Invalid uuid").to_string(), + }, + state: EntryNew, + attrs: self.attrs, + } + } + #[cfg(test)] pub unsafe fn to_valid_committed(self) -> Entry { Entry { - valid: EntryValid, + valid: EntryValid { + uuid: self.get_uuid().expect("Invalid uuid").to_string(), + }, state: EntryCommitted { id: 0 }, attrs: self.attrs, } } } -impl Entry { +impl Entry { #[cfg(test)] pub unsafe fn to_valid_committed(self) -> Entry { Entry { - valid: EntryValid, + valid: EntryValid { + uuid: self.get_uuid().expect("Invalid uuid").to_string(), + }, state: self.state, attrs: self.attrs, } @@ -411,30 +449,42 @@ impl Entry { } impl Entry { + #[cfg(test)] + pub unsafe fn to_valid_committed(self) -> Entry { + Entry { + valid: self.valid, + state: EntryCommitted { id: 0 }, + attrs: self.attrs, + } + } + pub fn compare(&self, rhs: &Entry) -> bool { self.attrs == rhs.attrs } } impl Entry { + #[cfg(test)] + pub unsafe fn to_valid_committed(self) -> Entry { + // NO-OP to satisfy macros. + self + } + pub fn compare(&self, rhs: &Entry) -> bool { self.attrs == rhs.attrs } pub fn to_tombstone(&self) -> Self { // Duplicate this to a tombstone entry. - let uuid_ava = self - .get_ava(&String::from("uuid")) - .expect("Corrupted entry!"); let class_ava = vec!["object".to_string(), "tombstone".to_string()]; let mut attrs_new: BTreeMap> = BTreeMap::new(); - attrs_new.insert("uuid".to_string(), uuid_ava.clone()); + attrs_new.insert("uuid".to_string(), vec![self.valid.uuid.clone()]); attrs_new.insert("class".to_string(), class_ava); Entry { - valid: EntryValid, + valid: self.valid.clone(), state: self.state, attrs: attrs_new, } @@ -445,12 +495,22 @@ impl Entry { } pub fn from_dbentry(db_e: DbEntry, id: u64) -> 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 { - valid: EntryValid, + valid: EntryValid { uuid: uuid }, state: EntryCommitted { id }, - attrs: match db_e.ent { - DbEntryVers::V1(v1) => v1.attrs, - }, + attrs: attrs, } } } @@ -481,25 +541,8 @@ impl Entry { } } - /* - pub fn seal(self) -> Entry { - Entry { - valid: self.valid, - state: EntryCommitted { - id: unimplemented!(), - }, - attrs: self.attrs, - } - } - */ - pub fn get_uuid(&self) -> &String { - // TODO: Make this not unwrap!!! - self.attrs - .get("uuid") - .expect("UUID ATTR NOT PRESENT, INVALID ENTRY STATE!!!") - .first() - .expect("UUID VALUE NOT PRESENT, INVALID ENTRY STATE!!!") + &self.valid.uuid } // Assert if this filter matches the entry (no index) diff --git a/src/lib/event.rs b/src/lib/event.rs index 6a40ffe1f..b25cd2da2 100644 --- a/src/lib/event.rs +++ b/src/lib/event.rs @@ -63,9 +63,70 @@ impl SearchResult { // At the top we get "event types" and they contain the needed // actions, and a generic event component. +#[derive(Debug, Clone)] +pub enum EventOrigin { + // External event, needs a UUID associated! Perhaps even an Entry/User to improve ACP checks? + User(String), + // Probably will bypass access profiles in many cases ... + Internal, + // Not used yet, but indicates that this change or event was triggered by a replication + // event - may not even be needed ... + // Replication, +} + +#[derive(Debug, Clone)] +pub struct Event { + // The event's initiator aka origin source. + // This importantly, is used for access control! + pub origin: EventOrigin, +} + +impl Event { + pub fn from_request( + _audit: &mut AuditScope, + // _qs: &QueryServerTransaction, + user_uuid: &str, + ) -> Result { + // Do we need to check or load the entry from the user_uuid? + // In the future, probably yes. + // + // For now, no. + Ok(Event { + origin: EventOrigin::User(user_uuid.to_string()), + }) + } + + pub fn from_internal() -> Self { + Event { + origin: EventOrigin::Internal, + } + } + + #[cfg(test)] + pub fn from_impersonate_uuid(uuid: &str) -> Self { + Event { + origin: EventOrigin::User(uuid.to_string()), + } + } + + pub fn from_impersonate(event: &Self) -> Self { + // TODO: In the future, we could change some of this data + // to reflect the fact we are infact impersonating the action + // rather than the user explicitly requesting it. Could matter + // to audits and logs to determine what happened. + event.clone() + } + + /* + pub fn is_internal(&self) -> bool { + match + } + */ +} + #[derive(Debug)] pub struct SearchEvent { - pub internal: bool, + pub event: Event, pub filter: Filter, // TODO: Add list of attributes to request } @@ -78,7 +139,11 @@ impl SearchEvent { ) -> Result { match Filter::from_ro(audit, &request.filter, qs) { Ok(f) => Ok(SearchEvent { - internal: false, + event: Event::from_request( + audit, + //qs, + request.user_uuid.as_str(), + )?, filter: Filter::new_ignore_hidden(f), }), Err(e) => Err(e), @@ -86,9 +151,17 @@ impl SearchEvent { } // Just impersonate the account with no filter changes. - pub fn new_impersonate(filter: Filter) -> Self { + #[cfg(test)] + pub fn new_impersonate_uuid(user_uuid: &str, filter: Filter) -> Self { SearchEvent { - internal: false, + event: Event::from_impersonate_uuid(user_uuid), + filter: filter, + } + } + + pub fn new_impersonate(event: &Event, filter: Filter) -> Self { + SearchEvent { + event: Event::from_impersonate(event), filter: filter, } } @@ -101,33 +174,38 @@ impl SearchEvent { ) -> Result { match Filter::from_ro(audit, &request.filter, qs) { Ok(f) => Ok(SearchEvent { + event: Event::from_request( + audit, + // qs, + request.user_uuid.as_str(), + )?, filter: Filter::new_recycled(f), - internal: false, }), Err(e) => Err(e), } } #[cfg(test)] - pub fn new_rec_impersonate(filter: Filter) -> Self { + /* Impersonate a request for recycled objects */ + pub fn new_rec_impersonate_uuid(user_uuid: &str, filter: Filter) -> Self { SearchEvent { - internal: false, + event: Event::from_impersonate_uuid(user_uuid), filter: Filter::new_recycled(filter), } } #[cfg(test)] - /* Impersonate an external request */ - pub fn new_ext_impersonate(filter: Filter) -> Self { + /* Impersonate an external request AKA filter ts + recycle */ + pub fn new_ext_impersonate_uuid(user_uuid: &str, filter: Filter) -> Self { SearchEvent { - internal: false, + event: Event::from_impersonate_uuid(user_uuid), filter: Filter::new_ignore_hidden(filter), } } pub fn new_internal(filter: Filter) -> Self { SearchEvent { - internal: true, + event: Event::from_internal(), filter: filter, } } @@ -138,12 +216,12 @@ impl SearchEvent { // request is internal or not. #[derive(Debug)] pub struct CreateEvent { + pub event: Event, // This may still actually change to handle the *raw* nature of the // input that we plan to parse. pub entries: Vec>, - /// Is the CreateEvent from an internal or external source? - /// This may affect which plugins are run ... - pub internal: bool, + // Is the CreateEvent from an internal or external source? + // This may affect which plugins are run ... } // FIXME: Should this actually be in createEvent handler? @@ -163,7 +241,11 @@ impl CreateEvent { // From ProtoEntry -> Entry // What is the correct consuming iterator here? Can we // even do that? - internal: false, + event: Event::from_request( + audit, + // qs, + request.user_uuid.as_str(), + )?, entries: entries, }), Err(e) => Err(e), @@ -172,16 +254,19 @@ impl CreateEvent { // Is this an internal only function? #[cfg(test)] - pub fn from_vec(entries: Vec>) -> Self { + pub fn new_impersonate_uuid( + user_uuid: &str, + entries: Vec>, + ) -> Self { CreateEvent { - internal: false, + event: Event::from_impersonate_uuid(user_uuid), entries: entries, } } pub fn new_internal(entries: Vec>) -> Self { CreateEvent { - internal: true, + event: Event::from_internal(), entries: entries, } } @@ -189,23 +274,23 @@ impl CreateEvent { #[derive(Debug)] pub struct ExistsEvent { + pub event: Event, pub filter: Filter, - pub internal: bool, } impl ExistsEvent { pub fn new_internal(filter: Filter) -> Self { ExistsEvent { + event: Event::from_internal(), filter: filter, - internal: true, } } } #[derive(Debug)] pub struct DeleteEvent { + pub event: Event, pub filter: Filter, - pub internal: bool, } impl DeleteEvent { @@ -216,34 +301,38 @@ impl DeleteEvent { ) -> Result { match Filter::from_rw(audit, &request.filter, qs) { Ok(f) => Ok(DeleteEvent { + event: Event::from_request( + audit, + // qs, + request.user_uuid.as_str(), + )?, filter: Filter::new_ignore_hidden(f), - internal: false, }), Err(e) => Err(e), } } #[cfg(test)] - pub fn from_filter(filter: Filter) -> Self { + pub fn new_impersonate_uuid(user_uuid: &str, filter: Filter) -> Self { DeleteEvent { + event: Event::from_impersonate_uuid(user_uuid), filter: filter, - internal: false, } } pub fn new_internal(filter: Filter) -> Self { DeleteEvent { + event: Event::from_internal(), filter: filter, - internal: true, } } } #[derive(Debug)] pub struct ModifyEvent { + pub event: Event, pub filter: Filter, pub modlist: ModifyList, - pub internal: bool, } impl ModifyEvent { @@ -255,9 +344,13 @@ impl ModifyEvent { match Filter::from_rw(audit, &request.filter, qs) { Ok(f) => match ModifyList::from(audit, &request.modlist, qs) { Ok(m) => Ok(ModifyEvent { + event: Event::from_request( + audit, + // qs, + request.user_uuid.as_str(), + )?, filter: Filter::new_ignore_hidden(f), modlist: m, - internal: false, }), Err(e) => Err(e), }, @@ -266,37 +359,44 @@ impl ModifyEvent { } } - #[cfg(test)] - pub fn from_filter(filter: Filter, modlist: ModifyList) -> Self { - ModifyEvent { - filter: filter, - modlist: modlist, - internal: false, - } - } - pub fn new_internal(filter: Filter, modlist: ModifyList) -> Self { ModifyEvent { + event: Event::from_internal(), filter: filter, modlist: modlist, - internal: true, } } - pub fn new_impersonate( + #[cfg(test)] + pub fn new_impersonate_uuid( + user_uuid: &str, filter: Filter, modlist: ModifyList, ) -> Self { ModifyEvent { + event: Event::from_impersonate_uuid(user_uuid), + filter: filter, + modlist: modlist, + } + } + + pub fn new_impersonate( + event: &Event, + filter: Filter, + modlist: ModifyList, + ) -> Self { + ModifyEvent { + event: Event::from_impersonate(event), filter: filter, modlist: modlist, - internal: false, } } } #[derive(Debug)] -pub struct AuthEvent {} +pub struct AuthEvent { + // pub event: Event, +} impl AuthEvent { pub fn from_request(_request: AuthRequest) -> Self { @@ -317,7 +417,9 @@ impl AuthResult { // TODO: Are these part of the proto? #[derive(Debug)] -pub struct PurgeTombstoneEvent {} +pub struct PurgeTombstoneEvent { + pub event: Event, +} impl Message for PurgeTombstoneEvent { type Result = (); @@ -325,12 +427,16 @@ impl Message for PurgeTombstoneEvent { impl PurgeTombstoneEvent { pub fn new() -> Self { - PurgeTombstoneEvent {} + PurgeTombstoneEvent { + event: Event::from_internal(), + } } } #[derive(Debug)] -pub struct PurgeRecycledEvent {} +pub struct PurgeRecycledEvent { + pub event: Event, +} impl Message for PurgeRecycledEvent { type Result = (); @@ -338,14 +444,16 @@ impl Message for PurgeRecycledEvent { impl PurgeRecycledEvent { pub fn new() -> Self { - PurgeRecycledEvent {} + PurgeRecycledEvent { + event: Event::from_internal(), + } } } #[derive(Debug)] pub struct ReviveRecycledEvent { + pub event: Event, pub filter: Filter, - pub internal: bool, } impl Message for ReviveRecycledEvent { @@ -360,8 +468,12 @@ impl ReviveRecycledEvent { ) -> Result { match Filter::from_rw(audit, &request.filter, qs) { Ok(f) => Ok(ReviveRecycledEvent { + event: Event::from_request( + audit, + // qs, + request.user_uuid.as_str(), + )?, filter: Filter::new_recycled(f), - internal: false, }), Err(e) => Err(e), } diff --git a/src/lib/filter.rs b/src/lib/filter.rs index c1259aeed..3b9802d84 100644 --- a/src/lib/filter.rs +++ b/src/lib/filter.rs @@ -419,10 +419,13 @@ mod tests { fn test_or_entry_filter() { let e: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1" + }, "state": null, "attrs": { "userid": ["william"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "uidNumber": ["1000"] } }"#, @@ -458,10 +461,13 @@ mod tests { fn test_and_entry_filter() { let e: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1" + }, "state": null, "attrs": { "userid": ["william"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "uidNumber": ["1000"] } }"#, @@ -497,10 +503,13 @@ mod tests { fn test_not_entry_filter() { let e1: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1" + }, "state": null, "attrs": { "userid": ["william"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "uidNumber": ["1000"] } }"#, @@ -524,10 +533,13 @@ mod tests { fn test_nested_entry_filter() { let e1: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1" + }, "state": null, "attrs": { "class": ["person"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "uidNumber": ["1000"] } }"#, @@ -536,10 +548,13 @@ mod tests { let e2: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "4b6228ab-1dbe-42a4-a9f5-f6368222438e" + }, "state": null, "attrs": { "class": ["person"], + "uuid": ["4b6228ab-1dbe-42a4-a9f5-f6368222438e"], "uidNumber": ["1001"] } }"#, @@ -548,10 +563,13 @@ mod tests { let e3: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "7b23c99d-c06b-4a9a-a958-3afa56383e1d" + }, "state": null, "attrs": { "class": ["person"], + "uuid": ["7b23c99d-c06b-4a9a-a958-3afa56383e1d"], "uidNumber": ["1002"] } }"#, @@ -560,10 +578,13 @@ mod tests { let e4: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "21d816b5-1f6a-4696-b7c1-6ed06d22ed81" + }, "state": null, "attrs": { "class": ["group"], + "uuid": ["21d816b5-1f6a-4696-b7c1-6ed06d22ed81"], "uidNumber": ["1000"] } }"#, diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 0c203c3e8..8d9bc68b3 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -34,7 +34,8 @@ mod log; #[macro_use] mod audit; mod be; -mod constants; +// TODO: Should this be public? +pub mod constants; mod entry; mod event; mod identity; diff --git a/src/lib/plugins/base.rs b/src/lib/plugins/base.rs index 2ed5f073e..f87600b59 100644 --- a/src/lib/plugins/base.rs +++ b/src/lib/plugins/base.rs @@ -48,7 +48,6 @@ impl Plugin for Base { cand: &mut Vec>, _ce: &CreateEvent, ) -> Result<(), OperationError> { - let name_uuid = String::from("uuid"); // For each candidate for entry in cand.iter_mut() { audit_log!(au, "Base check on entry: {:?}", entry); @@ -62,7 +61,7 @@ impl Plugin for Base { // 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(&name_uuid) { + let c_uuid: String = match entry.get_ava(&"uuid".to_string()) { Some(u) => { // Actually check we have a value, could be empty array ... // TODO: Should this be left to schema to assert the value? @@ -87,7 +86,7 @@ impl Plugin for Base { audit_log!(au, "Setting temporary UUID {} to entry", c_uuid); let ava_uuid: Vec = vec![c_uuid]; - entry.set_avas(name_uuid.clone(), ava_uuid); + entry.set_avas("uuid".to_string(), ava_uuid); audit_log!(au, "Temporary entry state: {:?}", entry); } @@ -98,7 +97,7 @@ impl Plugin for Base { // that a duplicate exists. for entry in cand.iter() { let uuid_ref = entry - .get_ava(&name_uuid) + .get_ava(&"uuid".to_string()) .ok_or(OperationError::Plugin)? .first() .ok_or(OperationError::Plugin)?; @@ -173,7 +172,6 @@ impl Plugin for Base { au: &mut AuditScope, qs: &QueryServerTransaction, ) -> Vec> { - let name_uuid = String::from("uuid"); // Verify all uuid's are unique? // Probably the literally worst thing ... @@ -190,35 +188,24 @@ impl Plugin for Base { .iter() // do an exists checks on the uuid .map(|e| { - // TODO: Could this be better? - let uuid = match e.get_ava(&name_uuid) { - Some(u) => { - if u.len() == 1 { - Ok(u.first().expect("Ohh ffs, really?").clone()) - } else { - Err(ConsistencyError::EntryUuidCorrupt(e.get_id())) - } - } - None => Err(ConsistencyError::EntryUuidCorrupt(e.get_id())), - }; + // To get the entry deserialised, a UUID MUST EXIST, else an expect + // will be thrown in the deserialise (possibly it will be better + // handled later). But it means this check only needs to validate + // uniqueness! + let uuid: &String = e.get_uuid(); - match uuid { - Ok(u) => { - let filt = Filter::Eq(name_uuid.clone(), u.clone()); - match qs.internal_search(au, filt) { - Ok(r) => { - if r.len() == 0 { - Err(ConsistencyError::UuidIndexCorrupt(u)) - } else if r.len() == 1 { - Ok(()) - } else { - Err(ConsistencyError::UuidNotUnique(u)) - } - } - Err(_) => Err(ConsistencyError::QueryServerSearchFailure), + let filt = Filter::Eq("uuid".to_string(), uuid.to_string()); + match qs.internal_search(au, filt) { + Ok(r) => { + if r.len() == 0 { + Err(ConsistencyError::UuidIndexCorrupt(uuid.to_string())) + } else if r.len() == 1 { + Ok(()) + } else { + Err(ConsistencyError::UuidNotUnique(uuid.to_string())) } } - Err(e) => Err(e), + Err(_) => Err(ConsistencyError::QueryServerSearchFailure), } }) .filter(|v| v.is_err()) @@ -277,7 +264,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { let cands = qs .internal_search(au, Filter::Eq("name".to_string(), "testperson".to_string())) @@ -314,7 +301,7 @@ mod tests { Err(OperationError::Plugin), preload, create, - false, + None, |_, _| {} ); } @@ -345,7 +332,7 @@ mod tests { Err(OperationError::Plugin), preload, create, - false, + None, |_, _| {} ); } @@ -376,7 +363,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { let cands = qs .internal_search(au, Filter::Eq("name".to_string(), "testperson".to_string())) @@ -412,7 +399,7 @@ mod tests { Err(OperationError::Plugin), preload, create, - false, + None, |_, _| {} ); } @@ -449,7 +436,7 @@ mod tests { Err(OperationError::Plugin), preload, create, - false, + None, |_, _| {} ); } @@ -495,7 +482,7 @@ mod tests { Err(OperationError::Plugin), preload, create, - false, + None, |_, _| {} ); } @@ -528,7 +515,7 @@ mod tests { "uuid".to_string(), "f15a7219-1d15-44e3-a7b4-bec899c07788".to_string() )]), - false, + None, |_, _| {} ); } @@ -560,7 +547,7 @@ mod tests { "uuid".to_string(), "f15a7219-1d15-44e3-a7b4-bec899c07788".to_string() )]), - false, + None, |_, _| {} ); } @@ -589,7 +576,7 @@ mod tests { preload, Filter::Eq("name".to_string(), "testgroup_a".to_string()), ModifyList::new_list(vec![Modify::Purged("uuid".to_string())]), - false, + None, |_, _| {} ); } diff --git a/src/lib/plugins/macros.rs b/src/lib/plugins/macros.rs index ba4d95072..ee3191b4f 100644 --- a/src/lib/plugins/macros.rs +++ b/src/lib/plugins/macros.rs @@ -34,7 +34,7 @@ macro_rules! run_create_test { $expect:expr, $preload_entries:ident, $create_entries:ident, - $internal:ident, + $internal:expr, $check:expr ) => {{ use crate::audit::AuditScope; @@ -48,10 +48,9 @@ macro_rules! run_create_test { audit_segment!(au, || { let qs = setup_test!(&mut au, $preload_entries); - let ce = if $internal { - CreateEvent::new_internal($create_entries.clone()) - } else { - CreateEvent::from_vec($create_entries.clone()) + let ce = match $internal { + None => CreateEvent::new_internal($create_entries.clone()), + Some(uuid) => CreateEvent::new_impersonate_uuid(uuid, $create_entries.clone()), }; let mut au_test = AuditScope::new("create_test"); @@ -86,7 +85,7 @@ macro_rules! run_modify_test { $preload_entries:ident, $modify_filter:expr, $modify_list:expr, - $internal:ident, + $internal:expr, $check:expr ) => {{ use crate::audit::AuditScope; @@ -100,10 +99,9 @@ macro_rules! run_modify_test { audit_segment!(au, || { let qs = setup_test!(&mut au, $preload_entries); - let me = if $internal { - ModifyEvent::new_internal($modify_filter, $modify_list) - } else { - ModifyEvent::from_filter($modify_filter, $modify_list) + let me = match $internal { + None => ModifyEvent::new_internal($modify_filter, $modify_list), + Some(uuid) => ModifyEvent::new_impersonate_uuid(uuid, $modify_filter, $modify_list), }; let mut au_test = AuditScope::new("modify_test"); @@ -137,7 +135,7 @@ macro_rules! run_delete_test { $expect:expr, $preload_entries:ident, $delete_filter:expr, - $internal:ident, + $internal:expr, $check:expr ) => {{ use crate::audit::AuditScope; @@ -151,10 +149,9 @@ macro_rules! run_delete_test { audit_segment!(au, || { let qs = setup_test!(&mut au, $preload_entries); - let de = if $internal { - DeleteEvent::new_internal($delete_filter.clone()) - } else { - DeleteEvent::from_filter($delete_filter.clone()) + let de = match $internal { + Some(uuid) => DeleteEvent::new_impersonate_uuid(uuid, $delete_filter.clone()), + None => DeleteEvent::new_internal($delete_filter.clone()), }; let mut au_test = AuditScope::new("delete_test"); diff --git a/src/lib/plugins/memberof.rs b/src/lib/plugins/memberof.rs index e9d6282ca..1d78cd6d3 100644 --- a/src/lib/plugins/memberof.rs +++ b/src/lib/plugins/memberof.rs @@ -509,7 +509,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -543,7 +543,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -598,7 +598,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -660,7 +660,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -727,7 +727,7 @@ mod tests { "member".to_string(), UUID_B.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -765,7 +765,7 @@ mod tests { "member".to_string(), UUID_B.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -821,7 +821,7 @@ mod tests { "member".to_string(), UUID_C.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -880,7 +880,7 @@ mod tests { "member".to_string(), UUID_A.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -950,7 +950,7 @@ mod tests { "member".to_string(), UUID_A.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1020,7 +1020,7 @@ mod tests { "member".to_string(), UUID_B.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1061,7 +1061,7 @@ mod tests { "member".to_string(), UUID_B.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1121,7 +1121,7 @@ mod tests { "member".to_string(), UUID_C.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1191,7 +1191,7 @@ mod tests { "member".to_string(), UUID_A.to_string() )]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1280,7 +1280,7 @@ mod tests { Modify::Removed("member".to_string(), UUID_A.to_string()), Modify::Removed("member".to_string(), UUID_D.to_string()), ]), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1344,7 +1344,7 @@ mod tests { Ok(()), preload, Filter::Eq("uuid".to_string(), UUID_A.to_string()), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1381,7 +1381,7 @@ mod tests { Ok(()), preload, Filter::Eq("uuid".to_string(), UUID_A.to_string()), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1428,7 +1428,7 @@ mod tests { Ok(()), preload, Filter::Eq("uuid".to_string(), UUID_B.to_string()), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1484,7 +1484,7 @@ mod tests { Ok(()), preload, Filter::Eq("uuid".to_string(), UUID_A.to_string()), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID @@ -1554,7 +1554,7 @@ mod tests { Ok(()), preload, Filter::Eq("uuid".to_string(), UUID_B.to_string()), - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { // V-- this uuid is // V-- memberof this UUID diff --git a/src/lib/plugins/recycle.rs b/src/lib/plugins/recycle.rs index 8b1378917..bd5b73c1a 100644 --- a/src/lib/plugins/recycle.rs +++ b/src/lib/plugins/recycle.rs @@ -1 +1,2 @@ - +// Don't allow setting class = recycle/tombstone during any +// operation unless internal == true OR delete. diff --git a/src/lib/plugins/refint.rs b/src/lib/plugins/refint.rs index 9b96eeb32..4010dbaf3 100644 --- a/src/lib/plugins/refint.rs +++ b/src/lib/plugins/refint.rs @@ -141,19 +141,12 @@ impl Plugin for ReferentialIntegrity { // Delete is pretty different to the other pre checks. This is // actually the bulk of the work we'll do to clean up references // when they are deleted. - let uuid_name = "uuid".to_string(); // Find all reference types in the schema let schema = qs.get_schema(); let ref_types = schema.get_reference_types(); // Get the UUID of all entries we are deleting - let uuids: Vec<&String> = cand - .iter() - .map(|e| e.get_ava(&uuid_name).ok_or(OperationError::Plugin)) - .collect::, _>>()? - .into_iter() - .flatten() - .collect(); + let uuids: Vec<&String> = cand.iter().map(|e| e.get_uuid()).collect(); // Generate a filter which is the set of all schema reference types // as EQ to all uuid of all entries in delete. - this INCLUDES recycled @@ -197,7 +190,6 @@ impl Plugin for ReferentialIntegrity { au: &mut AuditScope, qs: &QueryServerTransaction, ) -> Vec> { - let name_uuid = "uuid".to_string(); // Get all entries as cand // build a cand-uuid set let filt_in: Filter = @@ -211,30 +203,9 @@ impl Plugin for ReferentialIntegrity { Err(e) => return vec![e], }; - let (acu, err): ( - Vec>, - Vec>, - ) = all_cand - .iter() - .map(|e| { - e.get_ava(&name_uuid) - .ok_or(ConsistencyError::EntryUuidCorrupt(e.get_id())) - .map(|v| v.first().expect("Can not fail!!!")) - }) - .partition(|v| v.is_ok()); + let acu: Vec<&String> = all_cand.iter().map(|e| e.get_uuid()).collect(); - if err.len() > 0 { - return err - .into_iter() - .map(|v| Err(v.expect_err("Can not fail!!!"))) - .collect(); - } - - let acu_map: HashMap<&String, ()> = acu - .into_iter() - .map(|v| v.expect("Can not fail!!!")) - .map(|v| (v, ())) - .collect(); + let acu_map: HashMap<&String, ()> = acu.into_iter().map(|v| (v, ())).collect(); let schema = qs.get_schema(); let ref_types = schema.get_reference_types(); @@ -296,7 +267,7 @@ mod tests { Err(OperationError::Plugin), preload, create, - false, + None, |_, _| {} ); } @@ -339,7 +310,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { let cands = qs .internal_search( @@ -378,7 +349,7 @@ mod tests { Ok(()), preload, create, - false, + None, |au: &mut AuditScope, qs: &QueryServerWriteTransaction| { let cands = qs .internal_search(au, Filter::Eq("name".to_string(), "testgroup".to_string())) @@ -428,7 +399,7 @@ mod tests { "member".to_string(), "d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string() )]), - false, + None, |_, _| {} ); } @@ -459,7 +430,7 @@ mod tests { "member".to_string(), "d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string() )]), - false, + None, |_, _| {} ); } @@ -502,7 +473,7 @@ mod tests { preload, Filter::Eq("name".to_string(), "testgroup_b".to_string()), ModifyList::new_list(vec![Modify::Purged("member".to_string())]), - false, + None, |_, _| {} ); } @@ -534,7 +505,7 @@ mod tests { "member".to_string(), "d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string() )]), - false, + None, |_, _| {} ); } @@ -579,7 +550,7 @@ mod tests { "member".to_string(), "d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string() )]), - false, + None, |_, _| {} ); } @@ -623,7 +594,7 @@ mod tests { Ok(()), preload, Filter::Eq("name".to_string(), "testgroup_a".to_string()), - false, + None, |_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {} ); } @@ -671,7 +642,7 @@ mod tests { Ok(()), preload, Filter::Eq("name".to_string(), "testgroup_b".to_string()), - false, + None, |_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {} ); } @@ -700,7 +671,7 @@ mod tests { Ok(()), preload, Filter::Eq("name".to_string(), "testgroup_b".to_string()), - false, + None, |_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {} ); } diff --git a/src/lib/proto_v1.rs b/src/lib/proto_v1.rs index 69267ee58..a34edbdfc 100644 --- a/src/lib/proto_v1.rs +++ b/src/lib/proto_v1.rs @@ -59,11 +59,15 @@ impl OperationResponse { #[derive(Debug, Serialize, Deserialize)] pub struct SearchRequest { pub filter: Filter, + pub user_uuid: String, } impl SearchRequest { - pub fn new(filter: Filter) -> Self { - SearchRequest { filter: filter } + pub fn new(filter: Filter, user_uuid: &str) -> Self { + SearchRequest { + filter: filter, + user_uuid: user_uuid.to_string(), + } } } @@ -85,11 +89,15 @@ impl SearchResponse { #[derive(Debug, Serialize, Deserialize)] pub struct CreateRequest { pub entries: Vec, + pub user_uuid: String, } impl CreateRequest { - pub fn new(entries: Vec) -> Self { - CreateRequest { entries: entries } + pub fn new(entries: Vec, user_uuid: &str) -> Self { + CreateRequest { + entries: entries, + user_uuid: user_uuid.to_string(), + } } } @@ -100,11 +108,15 @@ impl Message for CreateRequest { #[derive(Debug, Serialize, Deserialize)] pub struct DeleteRequest { pub filter: Filter, + pub user_uuid: String, } impl DeleteRequest { - pub fn new(filter: Filter) -> Self { - DeleteRequest { filter: filter } + pub fn new(filter: Filter, user_uuid: &str) -> Self { + DeleteRequest { + filter: filter, + user_uuid: user_uuid.to_string(), + } } } @@ -117,13 +129,15 @@ pub struct ModifyRequest { // Probably needs a modlist? pub filter: Filter, pub modlist: ModifyList, + pub user_uuid: String, } impl ModifyRequest { - pub fn new(filter: Filter, modlist: ModifyList) -> Self { + pub fn new(filter: Filter, modlist: ModifyList, user_uuid: &str) -> Self { ModifyRequest { filter: filter, modlist: modlist, + user_uuid: user_uuid.to_string(), } } } @@ -159,6 +173,7 @@ pub enum AuthState { #[derive(Debug, Serialize, Deserialize)] pub struct AuthRequest { pub state: AuthState, + pub user_uuid: String, } impl Message for AuthRequest { @@ -191,11 +206,15 @@ pub struct AuthResponse { pub struct SearchRecycledRequest { pub filter: Filter, + pub user_uuid: String, } impl SearchRecycledRequest { - pub fn new(filter: Filter) -> Self { - SearchRecycledRequest { filter: filter } + pub fn new(filter: Filter, user_uuid: &str) -> Self { + SearchRecycledRequest { + filter: filter, + user_uuid: user_uuid.to_string(), + } } } @@ -203,10 +222,14 @@ impl SearchRecycledRequest { pub struct ReviveRecycledRequest { pub filter: Filter, + pub user_uuid: String, } impl ReviveRecycledRequest { - pub fn new(filter: Filter) -> Self { - ReviveRecycledRequest { filter: filter } + pub fn new(filter: Filter, user_uuid: &str) -> Self { + ReviveRecycledRequest { + filter: filter, + user_uuid: user_uuid.to_string(), + } } } diff --git a/src/lib/schema.rs b/src/lib/schema.rs index 33ea9102e..bcb50659c 100644 --- a/src/lib/schema.rs +++ b/src/lib/schema.rs @@ -1288,7 +1288,7 @@ mod tests { let mut audit = AuditScope::new("test_schema_entries"); let schema_outer = Schema::new(&mut audit).expect("failed to create schema"); let schema = schema_outer.read(); - let e_no_class: Entry = serde_json::from_str( + let e_no_uuid: Entry = serde_json::from_str( r#"{ "valid": null, "state": null, @@ -1297,6 +1297,22 @@ mod tests { ) .expect("json parse failure"); + assert_eq!( + e_no_uuid.validate(&schema), + Err(SchemaError::MissingMustAttribute("uuid".to_string())) + ); + + let e_no_class: Entry = serde_json::from_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"] + } + }"#, + ) + .expect("json parse failure"); + assert_eq!(e_no_class.validate(&schema), Err(SchemaError::InvalidClass)); let e_bad_class: Entry = serde_json::from_str( @@ -1304,6 +1320,7 @@ mod tests { "valid": null, "state": null, "attrs": { + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "class": ["zzzzzz"] } }"#, @@ -1319,6 +1336,7 @@ mod tests { "valid": null, "state": null, "attrs": { + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "class": ["attributetype"] } }"#, @@ -1343,6 +1361,7 @@ mod tests { "secret": ["false"], "multivalue": ["false"], "syntax": ["UTF8STRING"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "zzzzz": ["zzzz"] } }"#, @@ -1365,6 +1384,7 @@ mod tests { "system": ["false"], "secret": ["false"], "multivalue": ["zzzzz"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "syntax": ["UTF8STRING"] } }"#, @@ -1387,6 +1407,7 @@ mod tests { "system": ["false"], "secret": ["false"], "multivalue": ["true"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "syntax": ["UTF8STRING"] } }"#, @@ -1419,6 +1440,7 @@ mod tests { "name": ["TestPerson"], "displayName": ["testperson"], "syntax": ["utf8string"], + "UUID": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "index": ["equality"] } }"#, @@ -1427,13 +1449,16 @@ mod tests { let e_expect: Entry = serde_json::from_str( r#"{ - "valid": null, + "valid": { + "uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1" + }, "state": null, "attrs": { "class": ["extensibleobject"], "name": ["testperson"], "displayname": ["testperson"], "syntax": ["UTF8STRING"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "index": ["EQUALITY"] } }"#, @@ -1459,6 +1484,7 @@ mod tests { "state": null, "attrs": { "class": ["extensibleobject"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "secret": ["zzzz"] } }"#, @@ -1476,6 +1502,7 @@ mod tests { "state": null, "attrs": { "class": ["extensibleobject"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "secret": ["true"] } }"#, @@ -1511,6 +1538,7 @@ mod tests { "name": ["testperson"], "principal_name": ["testperson@project.org"], "description": ["testperson"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "displayname": ["testperson"] } }"#, @@ -1526,6 +1554,7 @@ mod tests { "class": ["group"], "name": ["testgroup"], "principal_name": ["testgroup@project.org"], + "uuid": ["db237e8a-0079-4b8c-8a56-593b22aa44d1"], "description": ["testperson"] } }"#, diff --git a/src/lib/server.rs b/src/lib/server.rs index e6f83bfa7..5ce521ddc 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -10,7 +10,8 @@ use crate::constants::{JSON_ANONYMOUS_V1, JSON_SYSTEM_INFO_V1}; use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; use crate::error::{ConsistencyError, OperationError, SchemaError}; use crate::event::{ - CreateEvent, DeleteEvent, ExistsEvent, ModifyEvent, ReviveRecycledEvent, SearchEvent, + CreateEvent, DeleteEvent, Event, EventOrigin, ExistsEvent, ModifyEvent, ReviveRecycledEvent, + SearchEvent, }; use crate::filter::{Filter, FilterInvalid}; use crate::modify::{Modify, ModifyInvalid, ModifyList}; @@ -164,13 +165,7 @@ pub trait QueryServerReadTransaction { // TODO: fine for 0/1 case, but check len for >= 2 to eliminate that case. let e = res.first().ok_or(OperationError::NoMatchingEntries)?; // Get the uuid from the entry. Again, check it exists, and only one. - let uuid_res = match e.get_ava(&String::from("uuid")) { - Some(vas) => match vas.first() { - Some(u) => u.clone(), - None => return Err(OperationError::InvalidEntryState), - }, - None => return Err(OperationError::InvalidEntryState), - }; + let uuid_res: String = e.get_uuid().to_string(); audit_log!(audit, "name_to_uuid: uuid <- {:?}", uuid_res); @@ -251,9 +246,10 @@ pub trait QueryServerReadTransaction { &self, audit: &mut AuditScope, filter: Filter, + event: &Event, ) -> Result>, OperationError> { let mut audit_int = AuditScope::new("impersonate_search"); - let se = SearchEvent::new_impersonate(filter); + let se = SearchEvent::new_impersonate(event, filter); let res = self.search(&mut audit_int, &se); audit.append_scope(audit_int); res @@ -583,7 +579,7 @@ impl<'a> QueryServerWriteTransaction<'a> { // We only need to retrieve uuid though ... // Now, delete only what you can see - let pre_candidates = match self.impersonate_search(au, de.filter.clone()) { + let pre_candidates = match self.impersonate_search(au, de.filter.clone(), &de.event) { Ok(results) => results, Err(e) => { audit_log!(au, "delete: error in pre-candidate selection {:?}", e); @@ -750,7 +746,7 @@ impl<'a> QueryServerWriteTransaction<'a> { )]); // Now impersonate the modify - self.impersonate_modify(au, re.filter.clone(), modlist) + self.impersonate_modify(au, re.filter.clone(), modlist, &re.event) } pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> { @@ -780,7 +776,7 @@ impl<'a> QueryServerWriteTransaction<'a> { // TODO: Fix this filter clone .... // Likely this will be fixed if search takes &filter, and then clone // to normalise, instead of attempting to mut the filter on norm. - let pre_candidates = match self.impersonate_search(au, me.filter.clone()) { + let pre_candidates = match self.impersonate_search(au, me.filter.clone(), &me.event) { Ok(results) => results, Err(e) => { audit_log!(au, "modify: error in pre-candidate selection {:?}", e); @@ -789,20 +785,23 @@ impl<'a> QueryServerWriteTransaction<'a> { }; if pre_candidates.len() == 0 { - if me.internal { - audit_log!( - au, - "modify: no candidates match filter ... continuing {:?}", - me.filter - ); - return Ok(()); - } else { - audit_log!( - au, - "modify: no candidates match filter, failure {:?}", - me.filter - ); - return Err(OperationError::NoMatchingEntries); + match me.event.origin { + EventOrigin::Internal => { + audit_log!( + au, + "modify: no candidates match filter ... continuing {:?}", + me.filter + ); + return Ok(()); + } + _ => { + audit_log!( + au, + "modify: no candidates match filter, failure {:?}", + me.filter + ); + return Err(OperationError::NoMatchingEntries); + } } }; @@ -937,9 +936,10 @@ impl<'a> QueryServerWriteTransaction<'a> { audit: &mut AuditScope, filter: Filter, modlist: ModifyList, + event: &Event, ) -> Result<(), OperationError> { let mut audit_int = AuditScope::new("impersonate_modify"); - let me = ModifyEvent::new_impersonate(filter, modlist); + let me = ModifyEvent::new_impersonate(event, filter, modlist); let res = self.modify(&mut audit_int, &me); audit.append_scope(audit_int); res @@ -1134,6 +1134,7 @@ mod tests { use crate::audit::AuditScope; use crate::be::Backend; + use crate::constants::UUID_ADMIN; use crate::entry::{Entry, EntryInvalid, EntryNew}; use crate::error::{OperationError, SchemaError}; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, ReviveRecycledEvent, SearchEvent}; @@ -1174,8 +1175,8 @@ mod tests { let server_txn = server.write(); let filt = Filter::Pres(String::from("name")); - let se1 = SearchEvent::new_impersonate(filt.clone()); - let se2 = SearchEvent::new_impersonate(filt); + let se1 = SearchEvent::new_impersonate_uuid(UUID_ADMIN, filt.clone()); + let se2 = SearchEvent::new_impersonate_uuid(UUID_ADMIN, filt); let e: Entry = serde_json::from_str( r#"{ @@ -1192,7 +1193,7 @@ mod tests { ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e.clone()]); + let ce = CreateEvent::new_internal(vec![e.clone()]); let r1 = server_txn.search(audit, &se1).expect("search failure"); assert!(r1.len() == 0); @@ -1276,20 +1277,21 @@ mod tests { ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1.clone(), e2.clone()]); + let ce = CreateEvent::new_internal(vec![e1.clone(), e2.clone()]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); // Empty Modlist (filter is valid) - let me_emp = ModifyEvent::from_filter( + let me_emp = ModifyEvent::new_internal( Filter::Pres(String::from("class")), ModifyList::new_list(vec![]), ); assert!(server_txn.modify(audit, &me_emp) == Err(OperationError::EmptyRequest)); // Mod changes no objects - let me_nochg = ModifyEvent::from_filter( + let me_nochg = ModifyEvent::new_impersonate_uuid( + UUID_ADMIN, Filter::Eq(String::from("name"), String::from("flarbalgarble")), ModifyList::new_list(vec![Modify::Present( String::from("description"), @@ -1299,7 +1301,7 @@ mod tests { assert!(server_txn.modify(audit, &me_nochg) == Err(OperationError::NoMatchingEntries)); // Filter is invalid to schema - let me_inv_f = ModifyEvent::from_filter( + let me_inv_f = ModifyEvent::new_internal( Filter::Eq(String::from("tnanuanou"), String::from("Flarbalgarble")), ModifyList::new_list(vec![Modify::Present( String::from("description"), @@ -1314,7 +1316,7 @@ mod tests { ); // Mod is invalid to schema - let me_inv_m = ModifyEvent::from_filter( + let me_inv_m = ModifyEvent::new_internal( Filter::Pres(String::from("class")), ModifyList::new_list(vec![Modify::Present( String::from("htnaonu"), @@ -1329,7 +1331,7 @@ mod tests { ); // Mod single object - let me_sin = ModifyEvent::from_filter( + let me_sin = ModifyEvent::new_internal( Filter::Eq(String::from("name"), String::from("testperson2")), ModifyList::new_list(vec![Modify::Present( String::from("description"), @@ -1339,7 +1341,7 @@ mod tests { assert!(server_txn.modify(audit, &me_sin).is_ok()); // Mod multiple object - let me_mult = ModifyEvent::from_filter( + let me_mult = ModifyEvent::new_internal( Filter::Or(vec![ Filter::Eq(String::from("name"), String::from("testperson1")), Filter::Eq(String::from("name"), String::from("testperson2")), @@ -1377,13 +1379,13 @@ mod tests { ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1.clone()]); + let ce = CreateEvent::new_internal(vec![e1.clone()]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); // Add class but no values - let me_sin = ModifyEvent::from_filter( + let me_sin = ModifyEvent::new_internal( Filter::Eq(String::from("name"), String::from("testperson1")), ModifyList::new_list(vec![Modify::Present( String::from("class"), @@ -1393,7 +1395,7 @@ mod tests { assert!(server_txn.modify(audit, &me_sin).is_err()); // Add multivalue where not valid - let me_sin = ModifyEvent::from_filter( + let me_sin = ModifyEvent::new_internal( Filter::Eq(String::from("name"), String::from("testperson1")), ModifyList::new_list(vec![Modify::Present( String::from("name"), @@ -1403,7 +1405,7 @@ mod tests { assert!(server_txn.modify(audit, &me_sin).is_err()); // add class and valid values? - let me_sin = ModifyEvent::from_filter( + let me_sin = ModifyEvent::new_internal( Filter::Eq(String::from("name"), String::from("testperson1")), ModifyList::new_list(vec![ Modify::Present(String::from("class"), String::from("system_info")), @@ -1414,7 +1416,7 @@ mod tests { assert!(server_txn.modify(audit, &me_sin).is_ok()); // Replace a value - let me_sin = ModifyEvent::from_filter( + let me_sin = ModifyEvent::new_internal( Filter::Eq(String::from("name"), String::from("testperson1")), ModifyList::new_list(vec![ Modify::Purged("name".to_string()), @@ -1476,31 +1478,31 @@ mod tests { ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1.clone(), e2.clone(), e3.clone()]); + let ce = CreateEvent::new_internal(vec![e1.clone(), e2.clone(), e3.clone()]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); // Delete filter is syntax invalid - let de_inv = DeleteEvent::from_filter(Filter::Pres(String::from("nhtoaunaoehtnu"))); + let de_inv = DeleteEvent::new_internal(Filter::Pres(String::from("nhtoaunaoehtnu"))); assert!(server_txn.delete(audit, &de_inv).is_err()); // Delete deletes nothing - let de_empty = DeleteEvent::from_filter(Filter::Eq( + let de_empty = DeleteEvent::new_internal(Filter::Eq( String::from("uuid"), String::from("cc8e95b4-c24f-4d68-ba54-000000000000"), )); assert!(server_txn.delete(audit, &de_empty).is_err()); // Delete matches one - let de_sin = DeleteEvent::from_filter(Filter::Eq( + let de_sin = DeleteEvent::new_internal(Filter::Eq( String::from("name"), String::from("testperson3"), )); assert!(server_txn.delete(audit, &de_sin).is_ok()); // Delete matches many - let de_mult = DeleteEvent::from_filter(Filter::Eq( + let de_mult = DeleteEvent::new_internal(Filter::Eq( String::from("description"), String::from("testperson"), )); @@ -1529,14 +1531,18 @@ mod tests { String::from("class"), String::from("tombstone"), )]), + UUID_ADMIN, ), &server_txn, ) .expect("modify event create failed"); - let de_ts = - DeleteEvent::from_request(audit, DeleteRequest::new(filt_ts.clone()), &server_txn) - .expect("delete event create failed"); - let se_ts = SearchEvent::new_ext_impersonate(filt_i_ts.clone()); + let de_ts = DeleteEvent::from_request( + audit, + DeleteRequest::new(filt_ts.clone(), UUID_ADMIN), + &server_txn, + ) + .expect("delete event create failed"); + let se_ts = SearchEvent::new_ext_impersonate_uuid(UUID_ADMIN, filt_i_ts.clone()); // First, create a tombstone let e_ts: Entry = serde_json::from_str( @@ -1551,7 +1557,7 @@ mod tests { ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e_ts]); + let ce = CreateEvent::new_internal(vec![e_ts]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); @@ -1610,23 +1616,27 @@ mod tests { String::from("class"), String::from("recycled"), )]), + UUID_ADMIN, ), &server_txn, ) .expect("modify event create failed"); - let de_rc = - DeleteEvent::from_request(audit, DeleteRequest::new(filt_rc.clone()), &server_txn) - .expect("delete event create failed"); - let se_rc = SearchEvent::new_ext_impersonate(filt_i_rc.clone()); + let de_rc = DeleteEvent::from_request( + audit, + DeleteRequest::new(filt_rc.clone(), UUID_ADMIN), + &server_txn, + ) + .expect("delete event create failed"); + let se_rc = SearchEvent::new_ext_impersonate_uuid(UUID_ADMIN, filt_i_rc.clone()); - let sre_rc = SearchEvent::new_rec_impersonate(filt_i_rc.clone()); + let sre_rc = SearchEvent::new_rec_impersonate_uuid(UUID_ADMIN, filt_i_rc.clone()); let rre_rc = ReviveRecycledEvent::from_request( audit, - ReviveRecycledRequest::new(ProtoFilter::Eq( - "name".to_string(), - "testperson1".to_string(), - )), + ReviveRecycledRequest::new( + ProtoFilter::Eq("name".to_string(), "testperson1".to_string()), + UUID_ADMIN, + ), &server_txn, ) .expect("revive recycled create failed"); @@ -1662,7 +1672,7 @@ mod tests { ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1, e2]); + let ce = CreateEvent::new_internal(vec![e1, e2]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); @@ -1739,19 +1749,19 @@ mod tests { }"#, ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1]); + let ce = CreateEvent::new_internal(vec![e1]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); // Delete and ensure they became recycled. - let de_sin = DeleteEvent::from_filter(Filter::Eq( + let de_sin = DeleteEvent::new_internal(Filter::Eq( String::from("name"), String::from("testperson1"), )); assert!(server_txn.delete(audit, &de_sin).is_ok()); // Can in be seen by special search? (external recycle search) let filt_rc = Filter::Eq(String::from("class"), String::from("recycled")); - let sre_rc = SearchEvent::new_rec_impersonate(filt_rc.clone()); + let sre_rc = SearchEvent::new_rec_impersonate_uuid(UUID_ADMIN, filt_rc.clone()); let r2 = server_txn.search(audit, &sre_rc).expect("search failed"); assert!(r2.len() == 1); @@ -1783,7 +1793,7 @@ mod tests { }"#, ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1]); + let ce = CreateEvent::new_internal(vec![e1]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); @@ -1821,7 +1831,7 @@ mod tests { }"#, ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1]); + let ce = CreateEvent::new_internal(vec![e1]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); @@ -1861,7 +1871,7 @@ mod tests { }"#, ) .expect("json failure"); - let ce = CreateEvent::from_vec(vec![e1]); + let ce = CreateEvent::new_internal(vec![e1]); let cr = server_txn.create(audit, &ce); assert!(cr.is_ok()); diff --git a/src/server/main.rs b/src/server/main.rs index d22c8a9c8..8e6a7fdcf 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -2,7 +2,6 @@ extern crate actix; extern crate rsidm; - use rsidm::config::Configuration; use rsidm::core::create_server_core; diff --git a/tests/proto_v1_test.rs b/tests/proto_v1_test.rs index 98b52e4ca..fe2a76532 100644 --- a/tests/proto_v1_test.rs +++ b/tests/proto_v1_test.rs @@ -3,6 +3,7 @@ use actix::prelude::*; extern crate rsidm; use rsidm::config::Configuration; +use rsidm::constants::UUID_ADMIN; use rsidm::core::create_server_core; use rsidm::proto_v1::{CreateRequest, Entry, OperationResponse}; @@ -68,7 +69,10 @@ fn test_server_proto() { ) .unwrap(); - let c = CreateRequest { entries: vec![e] }; + let c = CreateRequest { + entries: vec![e], + user_uuid: UUID_ADMIN.to_string(), + }; let mut response = client .post("http://127.0.0.1:8080/v1/create")