20190508 UUID on entry (#50)

* Make UUID a proper type on entries

* Add auth and ID data to relevant structures - this means we can start access controls!
This commit is contained in:
Firstyear 2019-05-15 10:36:18 +10:00 committed by GitHub
parent 9eca06c3e2
commit 44dc66713c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 545 additions and 299 deletions

View file

@ -806,6 +806,10 @@ mod tests {
let mut e: Entry<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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() };

View file

@ -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"],

View file

@ -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<VALID, STATE> {
@ -205,6 +211,13 @@ impl Entry<EntryInvalid, EntryNew> {
}
impl<STATE> Entry<EntryInvalid, STATE> {
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<STATE> Entry<EntryInvalid, STATE> {
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<STATE> Entry<EntryInvalid, STATE> {
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<STATE> Entry<EntryInvalid, STATE> {
impl<VALID, STATE> Clone for Entry<VALID, STATE>
where
VALID: Copy,
VALID: Clone,
STATE: Copy,
{
// Dirty modifiable state. Works on any other state to dirty them.
fn clone(&self) -> Entry<VALID, STATE> {
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<VALID, STATE> Entry<VALID, STATE> {
impl Entry<EntryInvalid, EntryCommitted> {
#[cfg(test)]
pub unsafe fn to_valid_new(self) -> Entry<EntryValid, EntryNew> {
Entry {
valid: EntryValid,
valid: EntryValid {
uuid: self.get_uuid().expect("Invalid uuid").to_string(),
},
state: EntryNew,
attrs: self.attrs,
}
@ -388,22 +411,37 @@ impl<VALID, STATE> Entry<VALID, STATE> {
}
// Both invalid states can be reached from "entry -> invalidate"
impl<VALID> Entry<VALID, EntryNew> {
impl Entry<EntryInvalid, EntryNew> {
#[cfg(test)]
pub unsafe fn to_valid_new(self) -> Entry<EntryValid, EntryNew> {
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<EntryValid, EntryCommitted> {
Entry {
valid: EntryValid,
valid: EntryValid {
uuid: self.get_uuid().expect("Invalid uuid").to_string(),
},
state: EntryCommitted { id: 0 },
attrs: self.attrs,
}
}
}
impl<VALID> Entry<VALID, EntryCommitted> {
impl Entry<EntryInvalid, EntryCommitted> {
#[cfg(test)]
pub unsafe fn to_valid_committed(self) -> Entry<EntryValid, EntryCommitted> {
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<VALID> Entry<VALID, EntryCommitted> {
}
impl Entry<EntryValid, EntryNew> {
#[cfg(test)]
pub unsafe fn to_valid_committed(self) -> Entry<EntryValid, EntryCommitted> {
Entry {
valid: self.valid,
state: EntryCommitted { id: 0 },
attrs: self.attrs,
}
}
pub fn compare(&self, rhs: &Entry<EntryValid, EntryCommitted>) -> bool {
self.attrs == rhs.attrs
}
}
impl Entry<EntryValid, EntryCommitted> {
#[cfg(test)]
pub unsafe fn to_valid_committed(self) -> Entry<EntryValid, EntryCommitted> {
// NO-OP to satisfy macros.
self
}
pub fn compare(&self, rhs: &Entry<EntryValid, EntryNew>) -> 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<String, Vec<String>> = 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<EntryValid, EntryCommitted> {
}
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<STATE> Entry<EntryValid, STATE> {
}
}
/*
pub fn seal(self) -> Entry<EntryValid, EntryCommitted> {
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)

View file

@ -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<Self, OperationError> {
// 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<FilterInvalid>,
// TODO: Add list of attributes to request
}
@ -78,7 +139,11 @@ impl SearchEvent {
) -> Result<Self, OperationError> {
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<FilterInvalid>) -> Self {
#[cfg(test)]
pub fn new_impersonate_uuid(user_uuid: &str, filter: Filter<FilterInvalid>) -> Self {
SearchEvent {
internal: false,
event: Event::from_impersonate_uuid(user_uuid),
filter: filter,
}
}
pub fn new_impersonate(event: &Event, filter: Filter<FilterInvalid>) -> Self {
SearchEvent {
event: Event::from_impersonate(event),
filter: filter,
}
}
@ -101,33 +174,38 @@ impl SearchEvent {
) -> Result<Self, OperationError> {
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<FilterInvalid>) -> Self {
/* Impersonate a request for recycled objects */
pub fn new_rec_impersonate_uuid(user_uuid: &str, filter: Filter<FilterInvalid>) -> 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<FilterInvalid>) -> Self {
/* Impersonate an external request AKA filter ts + recycle */
pub fn new_ext_impersonate_uuid(user_uuid: &str, filter: Filter<FilterInvalid>) -> Self {
SearchEvent {
internal: false,
event: Event::from_impersonate_uuid(user_uuid),
filter: Filter::new_ignore_hidden(filter),
}
}
pub fn new_internal(filter: Filter<FilterInvalid>) -> 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<Entry<EntryInvalid, EntryNew>>,
/// 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<Entry<EntryInvalid, EntryNew>>) -> Self {
pub fn new_impersonate_uuid(
user_uuid: &str,
entries: Vec<Entry<EntryInvalid, EntryNew>>,
) -> Self {
CreateEvent {
internal: false,
event: Event::from_impersonate_uuid(user_uuid),
entries: entries,
}
}
pub fn new_internal(entries: Vec<Entry<EntryInvalid, EntryNew>>) -> 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<FilterInvalid>,
pub internal: bool,
}
impl ExistsEvent {
pub fn new_internal(filter: Filter<FilterInvalid>) -> Self {
ExistsEvent {
event: Event::from_internal(),
filter: filter,
internal: true,
}
}
}
#[derive(Debug)]
pub struct DeleteEvent {
pub event: Event,
pub filter: Filter<FilterInvalid>,
pub internal: bool,
}
impl DeleteEvent {
@ -216,34 +301,38 @@ impl DeleteEvent {
) -> Result<Self, OperationError> {
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<FilterInvalid>) -> Self {
pub fn new_impersonate_uuid(user_uuid: &str, filter: Filter<FilterInvalid>) -> Self {
DeleteEvent {
event: Event::from_impersonate_uuid(user_uuid),
filter: filter,
internal: false,
}
}
pub fn new_internal(filter: Filter<FilterInvalid>) -> Self {
DeleteEvent {
event: Event::from_internal(),
filter: filter,
internal: true,
}
}
}
#[derive(Debug)]
pub struct ModifyEvent {
pub event: Event,
pub filter: Filter<FilterInvalid>,
pub modlist: ModifyList<ModifyInvalid>,
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<FilterInvalid>, modlist: ModifyList<ModifyInvalid>) -> Self {
ModifyEvent {
filter: filter,
modlist: modlist,
internal: false,
}
}
pub fn new_internal(filter: Filter<FilterInvalid>, modlist: ModifyList<ModifyInvalid>) -> 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<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
) -> Self {
ModifyEvent {
event: Event::from_impersonate_uuid(user_uuid),
filter: filter,
modlist: modlist,
}
}
pub fn new_impersonate(
event: &Event,
filter: Filter<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
) -> 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<FilterInvalid>,
pub internal: bool,
}
impl Message for ReviveRecycledEvent {
@ -360,8 +468,12 @@ impl ReviveRecycledEvent {
) -> Result<Self, OperationError> {
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),
}

View file

@ -419,10 +419,13 @@ mod tests {
fn test_or_entry_filter() {
let e: Entry<EntryValid, EntryNew> = 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<EntryValid, EntryNew> = 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<EntryValid, EntryNew> = 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<EntryValid, EntryNew> = 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<EntryValid, EntryNew> = 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<EntryValid, EntryNew> = 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<EntryValid, EntryNew> = 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"]
}
}"#,

View file

@ -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;

View file

@ -48,7 +48,6 @@ impl Plugin for Base {
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_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<String> = 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<Result<(), ConsistencyError>> {
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,
|_, _| {}
);
}

View file

@ -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");

View file

@ -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

View file

@ -1 +1,2 @@
// Don't allow setting class = recycle/tombstone during any
// operation unless internal == true OR delete.

View file

@ -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::<Result<Vec<_>, _>>()?
.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<Result<(), ConsistencyError>> {
let name_uuid = "uuid".to_string();
// Get all entries as cand
// build a cand-uuid set
let filt_in: Filter<FilterInvalid> =
@ -211,30 +203,9 @@ impl Plugin for ReferentialIntegrity {
Err(e) => return vec![e],
};
let (acu, err): (
Vec<Result<&String, ConsistencyError>>,
Vec<Result<&String, ConsistencyError>>,
) = 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| {}
);
}

View file

@ -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<Entry>,
pub user_uuid: String,
}
impl CreateRequest {
pub fn new(entries: Vec<Entry>) -> Self {
CreateRequest { entries: entries }
pub fn new(entries: Vec<Entry>, 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(),
}
}
}

View file

@ -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<EntryInvalid, EntryNew> = serde_json::from_str(
let e_no_uuid: Entry<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryValid, EntryNew> = 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"]
}
}"#,

View file

@ -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<FilterInvalid>,
event: &Event,
) -> Result<Vec<Entry<EntryValid, EntryCommitted>>, 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<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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());

View file

@ -2,7 +2,6 @@ extern crate actix;
extern crate rsidm;
use rsidm::config::Configuration;
use rsidm::core::create_server_core;

View file

@ -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")