20190730 66 value types ()

This implements strongly typed storage of data types in attribute values. This means that have the future ability to have tagged, hidden, complex or other datatypes in values rather than relying on string manipulations. It helps also to lift the burden on schema to only checking the values types on input from the protocol types, so that comparisons and other conversions will be faster. It also helps to strengthen and check values are valid earlier in conversions.
This commit is contained in:
Firstyear 2019-08-27 09:36:54 +10:00 committed by GitHub
parent 66b6aabae6
commit d0e62ad85a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 2919 additions and 1865 deletions

View file

@ -17,17 +17,27 @@
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
use std::collections::{BTreeMap, BTreeSet};
use uuid::Uuid;
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryNormalised, EntryReduced, EntryValid};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
use crate::error::OperationError;
use crate::filter::{Filter, FilterValid};
use crate::modify::Modify;
use crate::proto::v1::Filter as ProtoFilter;
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::PartialValue;
use crate::event::{CreateEvent, DeleteEvent, EventOrigin, ModifyEvent, SearchEvent};
lazy_static! {
static ref CLASS_ACS: PartialValue = PartialValue::new_class("access_control_search");
static ref CLASS_ACC: PartialValue = PartialValue::new_class("access_control_create");
static ref CLASS_ACD: PartialValue = PartialValue::new_class("access_control_delete");
static ref CLASS_ACM: PartialValue = PartialValue::new_class("access_control_modify");
static ref CLASS_ACP: PartialValue = PartialValue::new_class("access_control_profile");
}
// =========================================================================
// PARSE ENTRY TO ACP, AND ACP MANAGEMENT
// =========================================================================
@ -35,6 +45,7 @@ use crate::event::{CreateEvent, DeleteEvent, EventOrigin, ModifyEvent, SearchEve
#[derive(Debug, Clone)]
pub struct AccessControlSearch {
acp: AccessControlProfile,
// TODO: Should this change to Value? May help to reduce transformations during processing.
attrs: Vec<String>,
}
@ -44,7 +55,7 @@ impl AccessControlSearch {
qs: &QueryServerWriteTransaction,
value: &Entry<EntryValid, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", "access_control_search") {
if !value.attribute_value_pres("class", &CLASS_ACS) {
audit_log!(audit, "class access_control_search not present.");
return Err(OperationError::InvalidACPState(
"Missing access_control_search",
@ -54,9 +65,8 @@ impl AccessControlSearch {
let attrs = try_audit!(
audit,
value
.get_ava("acp_search_attr")
.get_ava_string("acp_search_attr")
.ok_or(OperationError::InvalidACPState("Missing acp_search_attr"))
.map(|vs: &Vec<String>| vs.clone())
);
let acp = AccessControlProfile::try_from(audit, qs, value)?;
@ -78,7 +88,7 @@ impl AccessControlSearch {
AccessControlSearch {
acp: AccessControlProfile {
name: name.to_string(),
uuid: uuid.to_string(),
uuid: Uuid::parse_str(uuid).unwrap(),
receiver: receiver,
targetscope: targetscope,
},
@ -98,7 +108,7 @@ impl AccessControlDelete {
qs: &QueryServerWriteTransaction,
value: &Entry<EntryValid, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", "access_control_delete") {
if !value.attribute_value_pres("class", &CLASS_ACD) {
audit_log!(audit, "class access_control_delete not present.");
return Err(OperationError::InvalidACPState(
"Missing access_control_delete",
@ -120,7 +130,7 @@ impl AccessControlDelete {
AccessControlDelete {
acp: AccessControlProfile {
name: name.to_string(),
uuid: uuid.to_string(),
uuid: Uuid::parse_str(uuid).unwrap(),
receiver: receiver,
targetscope: targetscope,
},
@ -141,7 +151,7 @@ impl AccessControlCreate {
qs: &QueryServerWriteTransaction,
value: &Entry<EntryValid, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", "access_control_create") {
if !value.attribute_value_pres("class", &CLASS_ACC) {
audit_log!(audit, "class access_control_create not present.");
return Err(OperationError::InvalidACPState(
"Missing access_control_create",
@ -149,13 +159,11 @@ impl AccessControlCreate {
}
let attrs = value
.get_ava("acp_create_attr")
.map(|vs: &Vec<String>| vs.clone())
.get_ava_opt_string("acp_create_attr")
.unwrap_or_else(|| Vec::new());
let classes = value
.get_ava("acp_create_class")
.map(|vs: &Vec<String>| vs.clone())
.get_ava_opt_string("acp_create_class")
.unwrap_or_else(|| Vec::new());
Ok(AccessControlCreate {
@ -177,7 +185,7 @@ impl AccessControlCreate {
AccessControlCreate {
acp: AccessControlProfile {
name: name.to_string(),
uuid: uuid.to_string(),
uuid: Uuid::parse_str(uuid).unwrap(),
receiver: receiver,
targetscope: targetscope,
},
@ -201,7 +209,7 @@ impl AccessControlModify {
qs: &QueryServerWriteTransaction,
value: &Entry<EntryValid, EntryCommitted>,
) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", "access_control_modify") {
if !value.attribute_value_pres("class", &CLASS_ACM) {
audit_log!(audit, "class access_control_modify not present.");
return Err(OperationError::InvalidACPState(
"Missing access_control_modify",
@ -209,18 +217,15 @@ impl AccessControlModify {
}
let presattrs = value
.get_ava("acp_modify_presentattr")
.map(|vs: &Vec<String>| vs.clone())
.get_ava_opt_string("acp_modify_presentattr")
.unwrap_or_else(|| Vec::new());
let remattrs = value
.get_ava("acp_modify_removedattr")
.map(|vs: &Vec<String>| vs.clone())
.get_ava_opt_string("acp_modify_removedattr")
.unwrap_or_else(|| Vec::new());
let classes = value
.get_ava("acp_modify_class")
.map(|vs: &Vec<String>| vs.clone())
.get_ava_opt_string("acp_modify_class")
.unwrap_or_else(|| Vec::new());
Ok(AccessControlModify {
@ -244,7 +249,7 @@ impl AccessControlModify {
AccessControlModify {
acp: AccessControlProfile {
name: name.to_string(),
uuid: uuid.to_string(),
uuid: Uuid::parse_str(uuid).unwrap(),
receiver: receiver,
targetscope: targetscope,
},
@ -261,7 +266,7 @@ impl AccessControlModify {
#[derive(Debug, Clone)]
struct AccessControlProfile {
name: String,
uuid: String,
uuid: Uuid,
receiver: Filter<FilterValid>,
targetscope: Filter<FilterValid>,
}
@ -273,7 +278,7 @@ impl AccessControlProfile {
value: &Entry<EntryValid, EntryCommitted>,
) -> Result<Self, OperationError> {
// Assert we have class access_control_profile
if !value.attribute_value_pres("class", "access_control_profile") {
if !value.attribute_value_pres("class", &CLASS_ACP) {
audit_log!(audit, "class access_control_profile not present.");
return Err(OperationError::InvalidACPState(
"Missing access_control_profile",
@ -284,32 +289,27 @@ impl AccessControlProfile {
let name = try_audit!(
audit,
value
.get_ava_single("name")
.get_ava_single_str("name")
.ok_or(OperationError::InvalidACPState("Missing name"))
);
)
.to_string();
// copy uuid
let uuid = value.get_uuid();
let uuid = value.get_uuid().clone();
// receiver, and turn to real filter
let receiver_raw = try_audit!(
let receiver_f: ProtoFilter = try_audit!(
audit,
value
.get_ava_single("acp_receiver")
.get_ava_single_protofilter("acp_receiver")
.ok_or(OperationError::InvalidACPState("Missing acp_receiver"))
);
// targetscope, and turn to real filter
let targetscope_raw = try_audit!(
let targetscope_f: ProtoFilter = try_audit!(
audit,
value
.get_ava_single("acp_targetscope")
.get_ava_single_protofilter("acp_targetscope")
.ok_or(OperationError::InvalidACPState("Missing acp_targetscope"))
);
audit_log!(audit, "RAW receiver {:?}", receiver_raw);
let receiver_f: ProtoFilter = try_audit!(
audit,
serde_json::from_str(receiver_raw.as_str())
.map_err(|_| OperationError::InvalidACPState("Invalid acp_receiver"))
);
let receiver_i = try_audit!(audit, Filter::from_rw(audit, &receiver_f, qs));
let receiver = try_audit!(
audit,
@ -318,14 +318,6 @@ impl AccessControlProfile {
.map_err(|e| OperationError::SchemaViolation(e))
);
audit_log!(audit, "RAW tscope {:?}", targetscope_raw);
let targetscope_f: ProtoFilter = try_audit!(
audit,
serde_json::from_str(targetscope_raw.as_str()).map_err(|e| {
audit_log!(audit, "JSON error {:?}", e);
OperationError::InvalidACPState("Invalid acp_targetscope")
})
);
let targetscope_i = try_audit!(audit, Filter::from_rw(audit, &targetscope_f, qs));
let targetscope = try_audit!(
audit,
@ -335,8 +327,8 @@ impl AccessControlProfile {
);
Ok(AccessControlProfile {
name: name.clone(),
uuid: uuid.clone(),
name: name,
uuid: uuid,
receiver: receiver,
targetscope: targetscope,
})
@ -350,10 +342,10 @@ impl AccessControlProfile {
#[derive(Debug, Clone)]
pub struct AccessControlsInner {
// What is the correct key here?
acps_search: BTreeMap<String, AccessControlSearch>,
acps_create: BTreeMap<String, AccessControlCreate>,
acps_modify: BTreeMap<String, AccessControlModify>,
acps_delete: BTreeMap<String, AccessControlDelete>,
acps_search: BTreeMap<Uuid, AccessControlSearch>,
acps_create: BTreeMap<Uuid, AccessControlCreate>,
acps_modify: BTreeMap<Uuid, AccessControlModify>,
acps_delete: BTreeMap<Uuid, AccessControlDelete>,
}
impl AccessControlsInner {
@ -719,14 +711,20 @@ pub trait AccessControlsTransaction {
.filter_map(|m| match m {
Modify::Present(a, v) => {
if a.as_str() == "class" {
Some(v.as_str())
// Here we have an option<&str> which could mean there is a risk of
// a malicious entity attempting to trick us by masking class mods
// in non-iutf8 types. However, the server first won't respect their
// existance, and second, we would have failed the mod at schema checking
// earlier in the process as these were not correctly type. As a result
// we can trust these to be correct here and not to be "None".
Some(v.to_str_unwrap())
} else {
None
}
}
Modify::Removed(a, v) => {
if a.as_str() == "class" {
Some(v.as_str())
Some(v.to_str_unwrap())
} else {
None
}
@ -817,7 +815,7 @@ pub trait AccessControlsTransaction {
&self,
audit: &mut AuditScope,
ce: &CreateEvent,
entries: &Vec<Entry<EntryNormalised, EntryNew>>,
entries: &Vec<Entry<EntryInvalid, EntryNew>>,
) -> Result<bool, OperationError> {
audit_log!(audit, "Access check for event: {:?}", ce);
@ -882,9 +880,12 @@ pub trait AccessControlsTransaction {
// I still think if this is None, we should just fail here ...
// because it shouldn't be possible to match.
let create_classes: BTreeSet<&str> = match e.get_ava_set("class") {
let create_classes: BTreeSet<&str> = match e.get_ava_set_str("class") {
Some(s) => s,
None => return false,
None => {
audit_log!(audit, "Class set failed to build - corrupted entry?");
return false;
}
};
related_acp.iter().fold(false, |r_acc, accr| {
@ -1188,6 +1189,7 @@ mod tests {
// use crate::filter::Filter;
// use crate::proto_v1::Filter as ProtoFilter;
use crate::constants::{JSON_ADMIN_V1, JSON_ANONYMOUS_V1, JSON_TESTPERSON1, JSON_TESTPERSON2};
use crate::value::{PartialValue, Value};
macro_rules! acp_from_entry_err {
(
@ -1196,7 +1198,7 @@ mod tests {
$e:expr,
$type:ty
) => {{
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str($e).expect("json failure");
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str($e);
let ev1 = unsafe { e1.to_valid_committed() };
let r1 = <$type>::try_from($audit, $qs, &ev1);
@ -1211,7 +1213,7 @@ mod tests {
$e:expr,
$type:ty
) => {{
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str($e).expect("json failure");
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str($e);
let ev1 = unsafe { e1.to_valid_committed() };
let r1 = <$type>::try_from($audit, $qs, &ev1);
@ -1677,7 +1679,7 @@ mod tests {
// Test that an internal search bypasses ACS
let se = unsafe { SearchEvent::new_internal_invalid(filter!(f_pres("class"))) };
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -1687,8 +1689,7 @@ mod tests {
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"]
}
}"#,
)
.expect("json failure");
);
let ev1 = unsafe { e1.to_valid_committed() };
let expect = vec![ev1.clone()];
@ -1714,12 +1715,10 @@ mod tests {
#[test]
fn test_access_enforce_search() {
// Test that entries from a search are reduced by acps
let e1: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TESTPERSON1).expect("json failure");
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
let ev1 = unsafe { e1.to_valid_committed() };
let e2: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TESTPERSON2).expect("json failure");
let e2: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON2);
let ev2 = unsafe { e2.to_valid_committed() };
let r_set = vec![ev1.clone(), ev2.clone()];
@ -1739,9 +1738,9 @@ mod tests {
"test_acp",
"d38640c4-0254-49f9-99b7-8ba7d0233f3d",
// apply to admin only
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// Allow admin to read only testperson1
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// In that read, admin may only view the "name" attribute, or query on
// the name attribute. Any other query (should be) rejected.
"name",
@ -1801,20 +1800,19 @@ mod tests {
// Test that attributes are correctly limited.
// In this case, we test that a user can only see "name" despite the
// class and uuid being present.
let e1: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TESTPERSON1).expect("json failure");
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
let ev1 = unsafe { e1.to_valid_committed() };
let r_set = vec![ev1.clone()];
let ex1: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TESTPERSON1_REDUCED).expect("json failure");
Entry::unsafe_from_entry_str(JSON_TESTPERSON1_REDUCED);
let exv1 = unsafe { ex1.to_valid_committed() };
let ex_anon = vec![exv1.clone()];
let se_anon = unsafe {
SearchEvent::new_impersonate_entry_ser(
JSON_ANONYMOUS_V1,
filter_all!(f_eq("name", "testperson1")),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
)
};
@ -1823,9 +1821,9 @@ mod tests {
"test_acp",
"d38640c4-0254-49f9-99b7-8ba7d0233f3d",
// apply to anonymous only
filter_valid!(f_eq("name", "anonymous")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("anonymous"))),
// Allow anonymous to read only testperson1
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// In that read, admin may only view the "name" attribute, or query on
// the name attribute. Any other query (should be) rejected.
"name",
@ -1861,8 +1859,7 @@ mod tests {
#[test]
fn test_access_enforce_modify() {
let e1: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TESTPERSON1).expect("json failure");
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
let ev1 = unsafe { e1.to_valid_committed() };
let r_set = vec![ev1.clone()];
@ -1870,23 +1867,23 @@ mod tests {
let me_pres = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
modlist!([m_pres("name", "value")]),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
modlist!([m_pres("name", &Value::new_iutf8s("value"))]),
)
};
// Name rem
let me_rem = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
modlist!([m_remove("name", "value")]),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
modlist!([m_remove("name", &PartialValue::new_iutf8s("value"))]),
)
};
// Name purge
let me_purge = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
modlist!([m_purge("name")]),
)
};
@ -1895,23 +1892,23 @@ mod tests {
let me_pres_class = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
modlist!([m_pres("class", "account")]),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
modlist!([m_pres("class", &Value::new_class("account"))]),
)
};
// Class account rem
let me_rem_class = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
modlist!([m_remove("class", "account")]),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
modlist!([m_remove("class", &PartialValue::new_class("account"))]),
)
};
// Class purge
let me_purge_class = unsafe {
ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
modlist!([m_purge("class")]),
)
};
@ -1922,9 +1919,9 @@ mod tests {
"test_modify_allow",
"87bfe9b8-7600-431e-a492-1dde64bbc455",
// Apply to admin
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// To modify testperson
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// Allow pres name and class
"name class",
// Allow rem name and class
@ -1939,9 +1936,9 @@ mod tests {
"test_modify_deny",
"87bfe9b8-7600-431e-a492-1dde64bbc456",
// Apply to admin
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// To modify testperson
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// Allow pres name and class
"member class",
// Allow rem name and class
@ -1956,9 +1953,9 @@ mod tests {
"test_modify_no_class",
"87bfe9b8-7600-431e-a492-1dde64bbc457",
// Apply to admin
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// To modify testperson
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// Allow pres name and class
"name class",
// Allow rem name and class
@ -2065,24 +2062,16 @@ mod tests {
#[test]
fn test_access_enforce_create() {
let e1: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TEST_CREATE_AC1).expect("json failure");
let ev1 = unsafe { e1.to_valid_normal() };
let ev1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC1);
let r1_set = vec![ev1.clone()];
let e2: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TEST_CREATE_AC2).expect("json failure");
let ev2 = unsafe { e2.to_valid_normal() };
let ev2: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC2);
let r2_set = vec![ev2.clone()];
let e3: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TEST_CREATE_AC3).expect("json failure");
let ev3 = unsafe { e3.to_valid_normal() };
let ev3: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC3);
let r3_set = vec![ev3.clone()];
let e4: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TEST_CREATE_AC4).expect("json failure");
let ev4 = unsafe { e4.to_valid_normal() };
let ev4: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TEST_CREATE_AC4);
let r4_set = vec![ev4.clone()];
// In this case, we can make the create event with an empty entry
@ -2097,10 +2086,10 @@ mod tests {
"test_create",
"87bfe9b8-7600-431e-a492-1dde64bbc453",
// Apply to admin
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// To create matching filter testperson
// Can this be empty?
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// classes
"account",
// attrs
@ -2113,9 +2102,9 @@ mod tests {
"test_create_2",
"87bfe9b8-7600-431e-a492-1dde64bbc454",
// Apply to admin
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// To create matching filter testperson
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
// classes
"group",
// attrs
@ -2158,22 +2147,21 @@ mod tests {
#[test]
fn test_access_enforce_delete() {
let e1: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_TESTPERSON1).expect("json failure");
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_TESTPERSON1);
let ev1 = unsafe { e1.to_valid_committed() };
let r_set = vec![ev1.clone()];
let de_admin = unsafe {
DeleteEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
filter_all!(f_eq("name", "testperson1")),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
)
};
let de_anon = unsafe {
DeleteEvent::new_impersonate_entry_ser(
JSON_ANONYMOUS_V1,
filter_all!(f_eq("name", "testperson1")),
filter_all!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
)
};
@ -2182,9 +2170,9 @@ mod tests {
"test_delete",
"87bfe9b8-7600-431e-a492-1dde64bbc453",
// Apply to admin
filter_valid!(f_eq("name", "admin")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("admin"))),
// To delete testperson
filter_valid!(f_eq("name", "testperson1")),
filter_valid!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
)
};

View file

@ -1,8 +1,9 @@
use crate::be::dbvalue::DbValueV1;
use std::collections::BTreeMap;
#[derive(Serialize, Deserialize, Debug)]
pub struct DbEntryV1 {
pub attrs: BTreeMap<String, Vec<String>>,
pub attrs: BTreeMap<String, Vec<DbValueV1>>,
}
// REMEMBER: If you add a new version here, you MUST

13
src/lib/be/dbvalue.rs Normal file
View file

@ -0,0 +1,13 @@
use uuid::Uuid;
#[derive(Serialize, Deserialize, Debug)]
pub enum DbValueV1 {
U8(String),
I8(String),
UU(Uuid),
BO(bool),
SY(usize),
IN(usize),
RF(Uuid),
JF(String),
}

View file

@ -16,6 +16,7 @@ use crate::error::{ConsistencyError, OperationError};
use crate::filter::{Filter, FilterValidResolved};
pub mod dbentry;
pub mod dbvalue;
mod idl;
mod mem_be;
mod sqlite_be;
@ -113,11 +114,12 @@ pub trait BackendTransaction {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let e =
match Entry::from_dbentry(db_e, id).ok_or(OperationError::CorruptedEntry) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let e = match Entry::from_dbentry(db_e, id)
.map_err(|_| OperationError::CorruptedEntry(id))
{
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if e.entry_match_no_index(&filt) {
Some(Ok(e))
} else {
@ -770,6 +772,7 @@ mod tests {
use super::super::audit::AuditScope;
use super::super::entry::{Entry, EntryInvalid, EntryNew};
use super::{Backend, BackendTransaction, BackendWriteTransaction, OperationError};
use crate::value::{PartialValue, Value};
macro_rules! run_test {
($test_fn:expr) => {{
@ -826,8 +829,8 @@ mod tests {
assert_eq!(empty_result, Err(OperationError::EmptyRequest));
let mut e: Entry<EntryInvalid, EntryNew> = Entry::new();
e.add_ava("userid", "william");
e.add_ava("uuid", "db237e8a-0079-4b8c-8a56-593b22aa44d1");
e.add_ava("userid", &Value::from("william"));
e.add_ava("uuid", &Value::from("db237e8a-0079-4b8c-8a56-593b22aa44d1"));
let e = unsafe { e.to_valid_new() };
let single_result = be.create(audit, &vec![e.clone()]);
@ -845,15 +848,16 @@ mod tests {
audit_log!(audit, "Simple Search");
let mut e: Entry<EntryInvalid, EntryNew> = Entry::new();
e.add_ava("userid", "claire");
e.add_ava("uuid", "db237e8a-0079-4b8c-8a56-593b22aa44d1");
e.add_ava("userid", &Value::from("claire"));
e.add_ava("uuid", &Value::from("db237e8a-0079-4b8c-8a56-593b22aa44d1"));
let e = unsafe { e.to_valid_new() };
let single_result = be.create(audit, &vec![e.clone()]);
assert!(single_result.is_ok());
// Test a simple EQ search
let filt = unsafe { filter_resolved!(f_eq("userid", "claire")) };
let filt =
unsafe { filter_resolved!(f_eq("userid", PartialValue::new_utf8s("claire"))) };
let r = be.search(audit, &filt);
assert!(r.expect("Search failed!").len() == 1);
@ -872,12 +876,12 @@ mod tests {
audit_log!(audit, "Simple Modify");
// First create some entries (3?)
let mut e1: Entry<EntryInvalid, EntryNew> = Entry::new();
e1.add_ava("userid", "william");
e1.add_ava("uuid", "db237e8a-0079-4b8c-8a56-593b22aa44d1");
e1.add_ava("userid", &Value::from("william"));
e1.add_ava("uuid", &Value::from("db237e8a-0079-4b8c-8a56-593b22aa44d1"));
let mut e2: Entry<EntryInvalid, EntryNew> = Entry::new();
e2.add_ava("userid", "alice");
e2.add_ava("uuid", "4b6228ab-1dbe-42a4-a9f5-f6368222438e");
e2.add_ava("userid", &Value::from("alice"));
e2.add_ava("uuid", &Value::from("4b6228ab-1dbe-42a4-a9f5-f6368222438e"));
let ve1 = unsafe { e1.clone().to_valid_new() };
let ve2 = unsafe { e2.clone().to_valid_new() };
@ -907,8 +911,8 @@ mod tests {
assert!(be.modify(audit, &vec![]).is_err());
// Make some changes to r1, r2.
r1.add_ava("desc", "modified");
r2.add_ava("desc", "modified");
r1.add_ava("desc", &Value::from("modified"));
r2.add_ava("desc", &Value::from("modified"));
// Now ... cheat.
@ -936,16 +940,16 @@ mod tests {
// First create some entries (3?)
let mut e1: Entry<EntryInvalid, EntryNew> = Entry::new();
e1.add_ava("userid", "william");
e1.add_ava("uuid", "db237e8a-0079-4b8c-8a56-593b22aa44d1");
e1.add_ava("userid", &Value::from("william"));
e1.add_ava("uuid", &Value::from("db237e8a-0079-4b8c-8a56-593b22aa44d1"));
let mut e2: Entry<EntryInvalid, EntryNew> = Entry::new();
e2.add_ava("userid", "alice");
e2.add_ava("uuid", "4b6228ab-1dbe-42a4-a9f5-f6368222438e");
e2.add_ava("userid", &Value::from("alice"));
e2.add_ava("uuid", &Value::from("4b6228ab-1dbe-42a4-a9f5-f6368222438e"));
let mut e3: Entry<EntryInvalid, EntryNew> = Entry::new();
e3.add_ava("userid", "lucy");
e3.add_ava("uuid", "7b23c99d-c06b-4a9a-a958-3afa56383e1d");
e3.add_ava("userid", &Value::from("lucy"));
e3.add_ava("uuid", &Value::from("7b23c99d-c06b-4a9a-a958-3afa56383e1d"));
let ve1 = unsafe { e1.clone().to_valid_new() };
let ve2 = unsafe { e2.clone().to_valid_new() };
@ -977,8 +981,8 @@ mod tests {
// WARNING: Normally, this isn't possible, but we are pursposefully breaking
// the state machine rules here!!!!
let mut e4: Entry<EntryInvalid, EntryNew> = Entry::new();
e4.add_ava("userid", "amy");
e4.add_ava("uuid", "21d816b5-1f6a-4696-b7c1-6ed06d22ed81");
e4.add_ava("userid", &Value::from("amy"));
e4.add_ava("uuid", &Value::from("21d816b5-1f6a-4696-b7c1-6ed06d22ed81"));
let ve4 = unsafe { e4.clone().to_valid_committed() };
@ -1006,16 +1010,16 @@ mod tests {
run_test!(|audit: &mut AuditScope, be: &BackendWriteTransaction| {
// First create some entries (3?)
let mut e1: Entry<EntryInvalid, EntryNew> = Entry::new();
e1.add_ava("userid", "william");
e1.add_ava("uuid", "db237e8a-0079-4b8c-8a56-593b22aa44d1");
e1.add_ava("userid", &Value::from("william"));
e1.add_ava("uuid", &Value::from("db237e8a-0079-4b8c-8a56-593b22aa44d1"));
let mut e2: Entry<EntryInvalid, EntryNew> = Entry::new();
e2.add_ava("userid", "alice");
e2.add_ava("uuid", "4b6228ab-1dbe-42a4-a9f5-f6368222438e");
e2.add_ava("userid", &Value::from("alice"));
e2.add_ava("uuid", &Value::from("4b6228ab-1dbe-42a4-a9f5-f6368222438e"));
let mut e3: Entry<EntryInvalid, EntryNew> = Entry::new();
e3.add_ava("userid", "lucy");
e3.add_ava("uuid", "7b23c99d-c06b-4a9a-a958-3afa56383e1d");
e3.add_ava("userid", &Value::from("lucy"));
e3.add_ava("uuid", &Value::from("7b23c99d-c06b-4a9a-a958-3afa56383e1d"));
let ve1 = unsafe { e1.clone().to_valid_new() };
let ve2 = unsafe { e2.clone().to_valid_new() };

View file

@ -1,3 +1,5 @@
use uuid::Uuid;
// On test builds, define to 60 seconds
#[cfg(test)]
pub static PURGE_TIMEOUT: u64 = 60;
@ -5,7 +7,15 @@ pub static PURGE_TIMEOUT: u64 = 60;
#[cfg(not(test))]
pub static PURGE_TIMEOUT: u64 = 3600;
pub static UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000";
pub static STR_UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000";
pub static STR_UUID_ANONYMOUS: &'static str = "00000000-0000-0000-0000-ffffffffffff";
pub static STR_UUID_DOES_NOT_EXIST: &'static str = "00000000-0000-0000-0000-fffffffffffe";
lazy_static! {
pub static ref UUID_ADMIN: Uuid = Uuid::parse_str(STR_UUID_ADMIN).unwrap();
pub static ref UUID_DOES_NOT_EXIST: Uuid = Uuid::parse_str(STR_UUID_DOES_NOT_EXIST).unwrap();
pub static ref UUID_ANONYMOUS: Uuid = Uuid::parse_str(STR_UUID_ANONYMOUS).unwrap();
}
pub static JSON_ADMIN_V1: &'static str = r#"{
"valid": {
"uuid": "00000000-0000-0000-0000-000000000000"
@ -117,9 +127,6 @@ pub static JSON_IDM_SELF_ACP_READ_V1: &'static str = r#"{
}
}"#;
pub static UUID_DOES_NOT_EXIST: &'static str = "00000000-0000-0000-0000-fffffffffffe";
pub static UUID_ANONYMOUS: &'static str = "00000000-0000-0000-0000-ffffffffffff";
pub static JSON_ANONYMOUS_V1: &'static str = r#"{
"valid": {
"uuid": "00000000-0000-0000-0000-ffffffffffff"

File diff suppressed because it is too large Load diff

View file

@ -16,17 +16,20 @@ pub enum OperationError {
EmptyRequest,
Backend,
NoMatchingEntries,
CorruptedEntry,
CorruptedEntry(u64),
ConsistencyError(Vec<Result<(), ConsistencyError>>),
SchemaViolation(SchemaError),
Plugin,
FilterGeneration,
FilterUUIDResolution,
InvalidAttributeName(String),
InvalidAttribute(&'static str),
InvalidDBState,
InvalidEntryID,
InvalidRequestState,
InvalidState,
InvalidEntryState,
InvalidUuid,
InvalidACPState(&'static str),
InvalidSchemaState(&'static str),
InvalidAccountState(&'static str),
@ -53,4 +56,5 @@ pub enum ConsistencyError {
UuidNotUnique(String),
RefintNotUpheld(u64),
MemberOfInvalid(u64),
InvalidAttributeType(&'static str),
}

View file

@ -87,7 +87,12 @@ impl Event {
// In the future, probably yes.
//
// For now, no.
let e = try_audit!(audit, qs.internal_search_uuid(audit, user_uuid));
let u = try_audit!(
audit,
Uuid::parse_str(user_uuid).map_err(|_| OperationError::InvalidUuid)
);
let e = try_audit!(audit, qs.internal_search_uuid(audit, &u));
Ok(Event {
origin: EventOrigin::User(e),
@ -101,8 +106,12 @@ impl Event {
) -> Result<Self, OperationError> {
audit_log!(audit, "from_ro_uat -> {:?}", uat);
let uat = uat.ok_or(OperationError::NotAuthenticated)?;
let u = try_audit!(
audit,
Uuid::parse_str(uat.uuid.as_str()).map_err(|_| OperationError::InvalidUuid)
);
let e = try_audit!(audit, qs.internal_search_uuid(audit, uat.uuid.as_str()));
let e = try_audit!(audit, qs.internal_search_uuid(audit, &u));
// TODO #64: Now apply claims from the uat into the Entry
// to allow filtering.
@ -120,7 +129,11 @@ impl Event {
// In the future, probably yes.
//
// For now, no.
let e = try_audit!(audit, qs.internal_search_uuid(audit, user_uuid));
let u = try_audit!(
audit,
Uuid::parse_str(user_uuid).map_err(|_| OperationError::InvalidUuid)
);
let e = try_audit!(audit, qs.internal_search_uuid(audit, &u));
Ok(Event {
origin: EventOrigin::User(e),
@ -142,8 +155,7 @@ impl Event {
#[cfg(test)]
pub unsafe fn from_impersonate_entry_ser(e: &str) -> Self {
let ei: Entry<EntryValid, EntryNew> =
serde_json::from_str(e).expect("Failed to deserialise!");
let ei: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(e);
Self::from_impersonate_entry(ei.to_valid_committed())
}

View file

@ -10,18 +10,21 @@ use crate::schema::SchemaTransaction;
use crate::server::{
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
};
use crate::value::PartialValue;
use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
// Default filter is safe, ignores all hidden types!
// This is &Value so we can lazy static then clone, but perhaps we can reconsider
// later if this should just take Value.
#[allow(dead_code)]
pub fn f_eq<'a>(a: &'a str, v: &'a str) -> FC<'a> {
pub fn f_eq<'a>(a: &'a str, v: PartialValue) -> FC<'a> {
FC::Eq(a, v)
}
#[allow(dead_code)]
pub fn f_sub<'a>(a: &'a str, v: &'a str) -> FC<'a> {
pub fn f_sub<'a>(a: &'a str, v: PartialValue) -> FC<'a> {
FC::Sub(a, v)
}
@ -54,8 +57,8 @@ pub fn f_self<'a>() -> FC<'a> {
// be transformed into a filter for the server to use.
#[derive(Debug, Deserialize)]
pub enum FC<'a> {
Eq(&'a str, &'a str),
Sub(&'a str, &'a str),
Eq(&'a str, PartialValue),
Sub(&'a str, PartialValue),
Pres(&'a str),
Or(Vec<FC<'a>>),
And(Vec<FC<'a>>),
@ -68,8 +71,8 @@ pub enum FC<'a> {
#[derive(Debug, Clone, PartialEq)]
enum FilterComp {
// This is attr - value
Eq(String, String),
Sub(String, String),
Eq(String, PartialValue),
Sub(String, PartialValue),
Pres(String),
Or(Vec<FilterComp>),
And(Vec<FilterComp>),
@ -86,8 +89,8 @@ enum FilterComp {
#[derive(Debug, Clone)]
pub enum FilterResolved {
// This is attr - value
Eq(String, String),
Sub(String, String),
Eq(String, PartialValue),
Sub(String, PartialValue),
Pres(String),
Or(Vec<FilterResolved>),
And(Vec<FilterResolved>),
@ -304,8 +307,8 @@ impl Filter<FilterInvalid> {
impl FilterComp {
fn new(fc: FC) -> Self {
match fc {
FC::Eq(a, v) => FilterComp::Eq(a.to_string(), v.to_string()),
FC::Sub(a, v) => FilterComp::Sub(a.to_string(), v.to_string()),
FC::Eq(a, v) => FilterComp::Eq(a.to_string(), v),
FC::Sub(a, v) => FilterComp::Sub(a.to_string(), v),
FC::Pres(a) => FilterComp::Pres(a.to_string()),
FC::Or(v) => FilterComp::Or(v.into_iter().map(|c| FilterComp::new(c)).collect()),
FC::And(v) => FilterComp::And(v.into_iter().map(|c| FilterComp::new(c)).collect()),
@ -317,8 +320,8 @@ impl FilterComp {
fn new_ignore_hidden(fc: FilterComp) -> Self {
FilterComp::And(vec![
FilterComp::AndNot(Box::new(FilterComp::Or(vec![
FilterComp::Eq("class".to_string(), "tombstone".to_string()),
FilterComp::Eq("class".to_string(), "recycled".to_string()),
FilterComp::Eq("class".to_string(), PartialValue::new_iutf8s("tombstone")),
FilterComp::Eq("class".to_string(), PartialValue::new_iutf8s("recycled")),
]))),
fc,
])
@ -326,7 +329,7 @@ impl FilterComp {
fn new_recycled(fc: FilterComp) -> Self {
FilterComp::And(vec![
FilterComp::Eq("class".to_string(), "recycled".to_string()),
FilterComp::Eq("class".to_string(), PartialValue::new_iutf8s("recycled")),
fc,
])
}
@ -359,22 +362,21 @@ impl FilterComp {
// Getting this each recursion could be slow. Maybe
// we need an inner functon that passes the reference?
let schema_attributes = schema.get_attributes();
let schema_name = schema_attributes
.get("name")
.expect("Critical: Core schema corrupt or missing.");
// We used to check the attr_name by normalising it (lowercasing)
// but should we? I think we actually should just call a special
// handler on schema to fix it up.
match self {
FilterComp::Eq(attr, value) => {
// Validate/normalise the attr name.
let attr_norm = schema_name.normalise_value(attr);
let attr_norm = schema.normalise_attr_name(attr);
// Now check it exists
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
let value_norm = schema_a.normalise_value(value);
schema_a
.validate_value(&value_norm)
.validate_partialvalue(&value)
// Okay, it worked, transform to a filter component
.map(|_| FilterComp::Eq(attr_norm, value_norm))
.map(|_| FilterComp::Eq(attr_norm, value.clone()))
// On error, pass the error back out.
}
None => Err(SchemaError::InvalidAttribute),
@ -382,22 +384,21 @@ impl FilterComp {
}
FilterComp::Sub(attr, value) => {
// Validate/normalise the attr name.
let attr_norm = schema_name.normalise_value(attr);
let attr_norm = schema.normalise_attr_name(attr);
// Now check it exists
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
let value_norm = schema_a.normalise_value(value);
schema_a
.validate_value(&value_norm)
.validate_partialvalue(&value)
// Okay, it worked, transform to a filter component
.map(|_| FilterComp::Sub(attr_norm, value_norm))
.map(|_| FilterComp::Sub(attr_norm, value.clone()))
// On error, pass the error back out.
}
None => Err(SchemaError::InvalidAttribute),
}
}
FilterComp::Pres(attr) => {
let attr_norm = schema_name.normalise_value(attr);
let attr_norm = schema.normalise_attr_name(attr);
// Now check it exists
match schema_attributes.get(&attr_norm) {
Some(_attr_name) => {
@ -454,8 +455,10 @@ impl FilterComp {
qs: &QueryServerReadTransaction,
) -> Result<Self, OperationError> {
Ok(match f {
ProtoFilter::Eq(a, v) => FilterComp::Eq(a.clone(), qs.clone_value(audit, a, v)?),
ProtoFilter::Sub(a, v) => FilterComp::Sub(a.clone(), qs.clone_value(audit, a, v)?),
ProtoFilter::Eq(a, v) => FilterComp::Eq(a.clone(), qs.clone_partialvalue(audit, a, v)?),
ProtoFilter::Sub(a, v) => {
FilterComp::Sub(a.clone(), qs.clone_partialvalue(audit, a, v)?)
}
ProtoFilter::Pres(a) => FilterComp::Pres(a.clone()),
ProtoFilter::Or(l) => FilterComp::Or(
l.iter()
@ -478,8 +481,10 @@ impl FilterComp {
qs: &QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
Ok(match f {
ProtoFilter::Eq(a, v) => FilterComp::Eq(a.clone(), qs.clone_value(audit, a, v)?),
ProtoFilter::Sub(a, v) => FilterComp::Sub(a.clone(), qs.clone_value(audit, a, v)?),
ProtoFilter::Eq(a, v) => FilterComp::Eq(a.clone(), qs.clone_partialvalue(audit, a, v)?),
ProtoFilter::Sub(a, v) => {
FilterComp::Sub(a.clone(), qs.clone_partialvalue(audit, a, v)?)
}
ProtoFilter::Pres(a) => FilterComp::Pres(a.clone()),
ProtoFilter::Or(l) => FilterComp::Or(
l.iter()
@ -644,7 +649,7 @@ impl FilterResolved {
FilterComp::SelfUUID => match &ev.origin {
EventOrigin::User(e) => Some(FilterResolved::Eq(
"uuid".to_string(),
e.get_uuid().to_string(),
PartialValue::new_uuid(e.get_uuid().clone()),
)),
_ => None,
},
@ -716,19 +721,22 @@ impl FilterResolved {
mod tests {
use crate::entry::{Entry, EntryNew, EntryValid};
use crate::filter::{Filter, FilterInvalid};
use serde_json;
use crate::value::PartialValue;
use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
#[test]
fn test_filter_simple() {
// Test construction.
let _filt: Filter<FilterInvalid> = filter!(f_eq("class", "user"));
let _filt: Filter<FilterInvalid> = filter!(f_eq("class", PartialValue::new_class("user")));
// AFTER
let _complex_filt: Filter<FilterInvalid> = filter!(f_and!([
f_or!([f_eq("userid", "test_a"), f_eq("userid", "test_b"),]),
f_sub("class", "user"),
f_or!([
f_eq("userid", PartialValue::new_iutf8s("test_a")),
f_eq("userid", PartialValue::new_iutf8s("test_b")),
]),
f_sub("class", PartialValue::new_class("user")),
]));
}
@ -758,32 +766,44 @@ mod tests {
fn test_filter_optimise() {
// Given sets of "optimisable" filters, optimise them.
filter_optimise_assert!(
f_and(vec![f_and(vec![f_eq("class", "test")])]),
f_and(vec![f_eq("class", "test")])
f_and(vec![f_and(vec![f_eq(
"class",
PartialValue::new_class("test")
)])]),
f_and(vec![f_eq("class", PartialValue::new_class("test"))])
);
filter_optimise_assert!(
f_or(vec![f_or(vec![f_eq("class", "test")])]),
f_or(vec![f_eq("class", "test")])
f_or(vec![f_or(vec![f_eq(
"class",
PartialValue::new_class("test")
)])]),
f_or(vec![f_eq("class", PartialValue::new_class("test"))])
);
filter_optimise_assert!(
f_and(vec![f_or(vec![f_and(vec![f_eq("class", "test")])])]),
f_and(vec![f_or(vec![f_and(vec![f_eq("class", "test")])])])
f_and(vec![f_or(vec![f_and(vec![f_eq(
"class",
PartialValue::new_class("test")
)])])]),
f_and(vec![f_or(vec![f_and(vec![f_eq(
"class",
PartialValue::new_class("test")
)])])])
);
// Later this can test duplicate filter detection.
filter_optimise_assert!(
f_and(vec![
f_and(vec![f_eq("class", "test")]),
f_sub("class", "te"),
f_and(vec![f_eq("class", PartialValue::new_class("test"))]),
f_sub("class", PartialValue::new_class("te")),
f_pres("class"),
f_eq("class", "test")
f_eq("class", PartialValue::new_class("test"))
]),
f_and(vec![
f_eq("class", "test"),
f_eq("class", PartialValue::new_class("test")),
f_pres("class"),
f_sub("class", "te"),
f_sub("class", PartialValue::new_class("te")),
])
);
@ -791,54 +811,54 @@ mod tests {
filter_optimise_assert!(
f_and(vec![
f_and(vec![
f_eq("class", "foo"),
f_eq("class", "test"),
f_eq("uid", "bar"),
f_eq("class", PartialValue::new_class("foo")),
f_eq("class", PartialValue::new_class("test")),
f_eq("uid", PartialValue::new_class("bar")),
]),
f_sub("class", "te"),
f_sub("class", PartialValue::new_class("te")),
f_pres("class"),
f_eq("class", "test")
f_eq("class", PartialValue::new_class("test"))
]),
f_and(vec![
f_eq("class", "foo"),
f_eq("class", "test"),
f_eq("uid", "bar"),
f_eq("class", PartialValue::new_class("foo")),
f_eq("class", PartialValue::new_class("test")),
f_eq("uid", PartialValue::new_class("bar")),
f_pres("class"),
f_sub("class", "te"),
f_sub("class", PartialValue::new_class("te")),
])
);
filter_optimise_assert!(
f_or(vec![
f_eq("class", "test"),
f_eq("class", PartialValue::new_class("test")),
f_pres("class"),
f_sub("class", "te"),
f_or(vec![f_eq("class", "test")]),
f_sub("class", PartialValue::new_class("te")),
f_or(vec![f_eq("class", PartialValue::new_class("test"))]),
]),
f_or(vec![
f_sub("class", "te"),
f_sub("class", PartialValue::new_class("te")),
f_pres("class"),
f_eq("class", "test")
f_eq("class", PartialValue::new_class("test"))
])
);
// Test dedup doesn't affect nested items incorrectly.
filter_optimise_assert!(
f_or(vec![
f_eq("class", "test"),
f_eq("class", PartialValue::new_class("test")),
f_and(vec![
f_eq("class", "test"),
f_eq("term", "test"),
f_or(vec![f_eq("class", "test")])
f_eq("class", PartialValue::new_class("test")),
f_eq("term", PartialValue::new_class("test")),
f_or(vec![f_eq("class", PartialValue::new_class("test"))])
]),
]),
f_or(vec![
f_and(vec![
f_eq("class", "test"),
f_eq("term", "test"),
f_or(vec![f_eq("class", "test")])
f_eq("class", PartialValue::new_class("test")),
f_eq("term", PartialValue::new_class("test")),
f_or(vec![f_eq("class", PartialValue::new_class("test"))])
]),
f_eq("class", "test"),
f_eq("class", PartialValue::new_class("test")),
])
);
}
@ -881,12 +901,12 @@ mod tests {
assert_eq!(f_t2b.partial_cmp(&f_t2a), Some(Ordering::Equal));
// antisymmetry: if a < b then !(a > b), as well as a > b implying !(a < b); and
let f_t3b = unsafe { filter_resolved!(f_eq("userid", "")) };
let f_t3b = unsafe { filter_resolved!(f_eq("userid", PartialValue::new_iutf8s(""))) };
assert_eq!(f_t1a.partial_cmp(&f_t3b), Some(Ordering::Greater));
assert_eq!(f_t3b.partial_cmp(&f_t1a), Some(Ordering::Less));
// transitivity: a < b and b < c implies a < c. The same must hold for both == and >.
let f_t4b = unsafe { filter_resolved!(f_sub("userid", "")) };
let f_t4b = unsafe { filter_resolved!(f_sub("userid", PartialValue::new_iutf8s(""))) };
assert_eq!(f_t1a.partial_cmp(&f_t4b), Some(Ordering::Less));
assert_eq!(f_t3b.partial_cmp(&f_t4b), Some(Ordering::Less));
@ -915,8 +935,9 @@ mod tests {
#[test]
fn test_or_entry_filter() {
let e: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1"
},
@ -927,40 +948,48 @@ mod tests {
"uidnumber": ["1000"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let f_t1a = unsafe {
filter_resolved!(f_or!([
f_eq("userid", "william"),
f_eq("uidnumber", "1000"),
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(e.entry_match_no_index(&f_t1a));
let f_t2a = unsafe {
filter_resolved!(f_or!([
f_eq("userid", "william"),
f_eq("uidnumber", "1001"),
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(e.entry_match_no_index(&f_t2a));
let f_t3a = unsafe {
filter_resolved!(f_or!([f_eq("userid", "alice"), f_eq("uidnumber", "1000"),]))
filter_resolved!(f_or!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(e.entry_match_no_index(&f_t3a));
let f_t4a = unsafe {
filter_resolved!(f_or!([f_eq("userid", "alice"), f_eq("uidnumber", "1001"),]))
filter_resolved!(f_or!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(!e.entry_match_no_index(&f_t4a));
}
#[test]
fn test_and_entry_filter() {
let e: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1"
},
@ -971,44 +1000,48 @@ mod tests {
"uidnumber": ["1000"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let f_t1a = unsafe {
filter_resolved!(f_and!([
f_eq("userid", "william"),
f_eq("uidnumber", "1000"),
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(e.entry_match_no_index(&f_t1a));
let f_t2a = unsafe {
filter_resolved!(f_and!([
f_eq("userid", "william"),
f_eq("uidnumber", "1001"),
f_eq("userid", PartialValue::new_iutf8s("william")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(!e.entry_match_no_index(&f_t2a));
let f_t3a = unsafe {
filter_resolved!(f_and!(
[f_eq("userid", "alice"), f_eq("uidnumber", "1000"),]
))
filter_resolved!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000")),
]))
};
assert!(!e.entry_match_no_index(&f_t3a));
let f_t4a = unsafe {
filter_resolved!(f_and!(
[f_eq("userid", "alice"), f_eq("uidnumber", "1001"),]
))
filter_resolved!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
]))
};
assert!(!e.entry_match_no_index(&f_t4a));
}
#[test]
fn test_not_entry_filter() {
let e1: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e1: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1"
},
@ -1019,20 +1052,29 @@ mod tests {
"uidnumber": ["1000"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let f_t1a = unsafe { filter_resolved!(f_andnot(f_eq("userid", "alice"))) };
let f_t1a = unsafe {
filter_resolved!(f_andnot(f_eq("userid", PartialValue::new_iutf8s("alice"))))
};
assert!(e1.entry_match_no_index(&f_t1a));
let f_t2a = unsafe { filter_resolved!(f_andnot(f_eq("userid", "william"))) };
let f_t2a = unsafe {
filter_resolved!(f_andnot(f_eq(
"userid",
PartialValue::new_iutf8s("william")
)))
};
assert!(!e1.entry_match_no_index(&f_t2a));
}
#[test]
fn test_nested_entry_filter() {
let e1: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e1: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "db237e8a-0079-4b8c-8a56-593b22aa44d1"
},
@ -1043,11 +1085,13 @@ mod tests {
"uidnumber": ["1000"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let e2: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e2: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "4b6228ab-1dbe-42a4-a9f5-f6368222438e"
},
@ -1058,11 +1102,13 @@ mod tests {
"uidnumber": ["1001"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let e3: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e3: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "7b23c99d-c06b-4a9a-a958-3afa56383e1d"
},
@ -1073,11 +1119,13 @@ mod tests {
"uidnumber": ["1002"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let e4: Entry<EntryValid, EntryNew> = serde_json::from_str(
r#"{
let e4: Entry<EntryValid, EntryNew> = unsafe {
Entry::unsafe_from_entry_str(
r#"{
"valid": {
"uuid": "21d816b5-1f6a-4696-b7c1-6ed06d22ed81"
},
@ -1088,13 +1136,17 @@ mod tests {
"uidnumber": ["1000"]
}
}"#,
)
.expect("Json parse failure");
)
.to_valid_new()
};
let f_t1a = unsafe {
filter_resolved!(f_and!([
f_eq("class", "person"),
f_or!([f_eq("uidnumber", "1001"), f_eq("uidnumber", "1000")])
f_eq("class", PartialValue::new_class("person")),
f_or!([
f_eq("uidnumber", PartialValue::new_iutf8s("1001")),
f_eq("uidnumber", PartialValue::new_iutf8s("1000"))
])
]))
};
@ -1109,17 +1161,22 @@ mod tests {
let mut f_expect = BTreeSet::new();
f_expect.insert("userid");
f_expect.insert("class");
// Given filters, get their expected attribute sets.
let f_t1a =
unsafe { filter_valid!(f_and!([f_eq("userid", "alice"), f_eq("class", "1001"),])) };
// Given filters, get their expected attribute sets - this is used by access control profiles
// to determine what attrs we are requesting regardless of the partialvalue.
let f_t1a = unsafe {
filter_valid!(f_and!([
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("class", PartialValue::new_iutf8s("1001")),
]))
};
assert!(f_t1a.get_attr_set() == f_expect);
let f_t2a = unsafe {
filter_valid!(f_and!([
f_eq("userid", "alice"),
f_eq("class", "1001"),
f_eq("userid", "claire"),
f_eq("userid", PartialValue::new_iutf8s("alice")),
f_eq("class", PartialValue::new_iutf8s("1001")),
f_eq("userid", PartialValue::new_iutf8s("claire")),
]))
};

View file

@ -5,6 +5,13 @@ use crate::proto::v1::UserAuthToken;
use crate::idm::claim::Claim;
use crate::idm::group::Group;
use crate::value::PartialValue;
use uuid::Uuid;
lazy_static! {
static ref PVCLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
}
#[derive(Debug, Clone)]
pub(crate) struct Account {
@ -16,7 +23,7 @@ pub(crate) struct Account {
// william problem I think :)
pub name: String,
pub displayname: String,
pub uuid: String,
pub uuid: Uuid,
pub groups: Vec<Group>,
// creds (various types)
// groups?
@ -30,26 +37,23 @@ impl Account {
value: Entry<EntryValid, EntryCommitted>,
) -> Result<Self, OperationError> {
// Check the classes
if !value.attribute_value_pres("class", "account") {
if !value.attribute_value_pres("class", &PVCLASS_ACCOUNT) {
return Err(OperationError::InvalidAccountState(
"Missing class: account",
));
}
// Now extract our needed attributes
let name = value
.get_ava_single("name")
.ok_or(OperationError::InvalidAccountState(
"Missing attribute: name",
))?
.clone();
let name =
value
.get_ava_single_string("name")
.ok_or(OperationError::InvalidAccountState(
"Missing attribute: name",
))?;
let displayname = value
.get_ava_single("displayname")
.ok_or(OperationError::InvalidAccountState(
"Missing attribute: displayname",
))?
.clone();
let displayname = value.get_ava_single_string("displayname").ok_or(
OperationError::InvalidAccountState("Missing attribute: displayname"),
)?;
// TODO #71: Resolve groups!!!!
let groups = Vec::new();
@ -75,7 +79,7 @@ impl Account {
Some(UserAuthToken {
name: self.name.clone(),
displayname: self.name.clone(),
uuid: self.uuid.clone(),
uuid: self.uuid.to_hyphenated_ref().to_string(),
application: None,
groups: self.groups.iter().map(|g| g.into_proto()).collect(),
claims: claims.iter().map(|c| c.into_proto()).collect(),
@ -96,7 +100,7 @@ mod tests {
#[test]
fn test_idm_account_from_anonymous() {
let anon_e: Entry<EntryValid, EntryNew> =
serde_json::from_str(JSON_ANONYMOUS_V1).expect("Json deserialise failure!");
unsafe { Entry::unsafe_from_entry_str(JSON_ANONYMOUS_V1).to_valid_new() };
let anon_e = unsafe { anon_e.to_valid_committed() };
let anon_account = Account::try_from_entry(anon_e).expect("Must not fail");

View file

@ -101,7 +101,7 @@ impl AuthSession {
// We want the primary handler - this is where we make a decision
// based on the anonymous ... in theory this could be cleaner
// and interact with the account more?
if account.uuid == UUID_ANONYMOUS {
if account.uuid == UUID_ANONYMOUS.clone() {
CredHandler::Anonymous
} else {
unimplemented!();

View file

@ -5,7 +5,7 @@ macro_rules! entry_str_to_account {
use crate::idm::account::Account;
let e: Entry<EntryValid, EntryNew> =
serde_json::from_str($entry_str).expect("Json deserialise failure!");
unsafe { Entry::unsafe_from_entry_str($entry_str).to_valid_new() };
let e = unsafe { e.to_valid_committed() };
Account::try_from_entry(e).expect("Account conversion failure")

View file

@ -5,8 +5,9 @@ use crate::idm::account::Account;
use crate::idm::authsession::AuthSession;
use crate::proto::v1::AuthState;
use crate::server::{QueryServer, QueryServerTransaction};
use concread::cowcell::{CowCell, CowCellWriteTxn};
use crate::value::PartialValue;
use concread::cowcell::{CowCell, CowCellWriteTxn};
use std::collections::BTreeMap;
use uuid::Uuid;
// use lru::LruCache;
@ -94,7 +95,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
// because it associates to the nonce's etc which were all cached.
let filter_entry = filter!(f_or!([
f_eq("name", init.name.as_str()),
f_eq("name", PartialValue::new_iutf8s(init.name.as_str())),
// This currently says invalid syntax, which is correct, but also
// annoying because it would be nice to search both ...
// f_eq("uuid", name.as_str()),

View file

@ -49,6 +49,7 @@ mod event;
mod filter;
mod interval;
mod modify;
mod value;
#[macro_use]
mod plugins;
mod access;

View file

@ -30,7 +30,9 @@ macro_rules! run_test {
$test_fn(&test_server, &mut audit);
// Any needed teardown?
// Make sure there are no errors.
assert!(test_server.verify(&mut audit).len() == 0);
let verifications = test_server.verify(&mut audit);
audit_log!(audit, "Verification result: {:?}", verifications);
assert!(verifications.len() == 0);
}};
}
@ -148,3 +150,54 @@ macro_rules! filter_resolved {
f.to_valid_resolved()
}};
}
#[cfg(test)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! pvalue_utf8 {
(
$v:expr
) => {{
use crate::value::PartialValue;
PartialValue::new_utf8(v.to_string())
}};
}
#[cfg(test)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! pvalue_iutf8 {
(
$v:expr
) => {{
use crate::value::PartialValue;
PartialValue::new_iutf8(v.to_string())
}};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! btreeset {
() => (
compile_error!("BTreeSet needs at least 1 element")
);
($e:expr) => ({
use std::collections::BTreeSet;
let mut x: BTreeSet<_> = BTreeSet::new();
assert!(x.insert($e));
x
});
($e:expr,) => ({
use std::collections::BTreeSet;
let mut x: BTreeSet<_> = BTreeSet::new();
assert!(x.insert($e));
x
});
($e:expr, $($item:expr),*) => ({
use std::collections::BTreeSet;
let mut x: BTreeSet<_> = BTreeSet::new();
assert!(x.insert($e));
$(assert!(x.insert($item));)*
x
});
}

View file

@ -5,6 +5,7 @@ use crate::proto::v1::ModifyList as ProtoModifyList;
use crate::error::{OperationError, SchemaError};
use crate::schema::SchemaTransaction;
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
// Should this be std?
use std::slice;
@ -17,21 +18,21 @@ pub struct ModifyInvalid;
#[derive(Serialize, Deserialize, Debug)]
pub enum Modify {
// This value *should* exist.
Present(String, String),
Present(String, Value),
// This value *should not* exist.
Removed(String, String),
Removed(String, PartialValue),
// This attr *should not* exist.
Purged(String),
}
#[allow(dead_code)]
pub fn m_pres(a: &str, v: &str) -> Modify {
Modify::Present(a.to_string(), v.to_string())
pub fn m_pres(a: &str, v: &Value) -> Modify {
Modify::Present(a.to_string(), v.clone())
}
#[allow(dead_code)]
pub fn m_remove(a: &str, v: &str) -> Modify {
Modify::Removed(a.to_string(), v.to_string())
pub fn m_remove(a: &str, v: &PartialValue) -> Modify {
Modify::Removed(a.to_string(), v.clone())
}
#[allow(dead_code)]
@ -47,7 +48,9 @@ impl Modify {
) -> Result<Self, OperationError> {
Ok(match m {
ProtoModify::Present(a, v) => Modify::Present(a.clone(), qs.clone_value(audit, a, v)?),
ProtoModify::Removed(a, v) => Modify::Removed(a.clone(), qs.clone_value(audit, a, v)?),
ProtoModify::Removed(a, v) => {
Modify::Removed(a.clone(), qs.clone_partialvalue(audit, a, v)?)
}
ProtoModify::Purged(a) => Modify::Purged(a.clone()),
})
}
@ -113,39 +116,36 @@ impl ModifyList<ModifyInvalid> {
schema: &SchemaTransaction,
) -> Result<ModifyList<ModifyValid>, SchemaError> {
let schema_attributes = schema.get_attributes();
/*
let schema_name = schema_attributes
.get("name")
.expect("Critical: Core schema corrupt or missing. To initiate a core transfer, please deposit substitute core in receptacle.");
*/
let res: Result<Vec<Modify>, _> = (&self.mods)
.into_iter()
.map(|m| match m {
Modify::Present(attr, value) => {
let attr_norm = schema_name.normalise_value(&attr);
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
let value_norm = schema_a.normalise_value(&value);
schema_a
.validate_value(&value_norm)
.map(|_| Modify::Present(attr_norm, value_norm))
}
Some(schema_a) => schema_a
.validate_value(&value)
.map(|_| Modify::Present(attr_norm, value.clone())),
None => Err(SchemaError::InvalidAttribute),
}
}
// TODO: Should this be a partial value type?
Modify::Removed(attr, value) => {
let attr_norm = schema_name.normalise_value(&attr);
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(schema_a) => {
let value_norm = schema_a.normalise_value(&value);
schema_a
.validate_value(&value_norm)
.map(|_| Modify::Removed(attr_norm, value_norm))
}
Some(schema_a) => schema_a
.validate_partialvalue(&value)
.map(|_| Modify::Removed(attr_norm, value.clone())),
None => Err(SchemaError::InvalidAttribute),
}
}
Modify::Purged(attr) => {
let attr_norm = schema_name.normalise_value(&attr);
let attr_norm = schema.normalise_attr_name(attr);
match schema_attributes.get(&attr_norm) {
Some(_attr_name) => Ok(Modify::Purged(attr_norm)),
None => Err(SchemaError::InvalidAttribute),

View file

@ -1,8 +1,10 @@
use crate::plugins::Plugin;
use std::collections::BTreeSet;
// TODO: Should be able to generate all uuid's via Value.
use uuid::Uuid;
use crate::audit::AuditScope;
// use crate::constants::{STR_UUID_ADMIN, STR_UUID_ANONYMOUS, STR_UUID_DOES_NOT_EXIST};
use crate::constants::{UUID_ADMIN, UUID_ANONYMOUS, UUID_DOES_NOT_EXIST};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
use crate::error::{ConsistencyError, OperationError};
@ -11,6 +13,11 @@ use crate::modify::Modify;
use crate::server::{
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
};
use crate::value::{PartialValue, Value};
lazy_static! {
static ref CLASS_OBJECT: Value = Value::new_class("object");
}
// This module has some special properties around it's operation, namely that it
// has to make a certain number of assertions *early* in the entry lifecycle around
@ -41,19 +48,20 @@ impl Plugin for Base {
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
ce: &CreateEvent,
) -> Result<(), OperationError> {
debug!("Entering base pre_create_transform");
// For each candidate
for entry in cand.iter_mut() {
audit_log!(au, "Base check on entry: {:?}", entry);
// First, ensure we have the 'object', class in the class set.
entry.add_ava("class", "object");
entry.add_ava("class", &CLASS_OBJECT);
audit_log!(au, "Object should now be in entry: {:?}", entry);
// If they have a name, but no principal name, derive it.
// if they don't have uuid, create it.
let c_uuid: String = match entry.get_ava("uuid") {
let c_uuid: Value = match entry.get_ava("uuid") {
Some(u) => {
// Actually check we have a value, could be empty array ...
if u.len() > 1 {
@ -67,34 +75,40 @@ impl Plugin for Base {
// Should this be forgiving and just generate the UUID?
// NO! If you tried to specify it, but didn't give it, then you made
// a mistake and your intent is unknown.
try_audit!(
let v: Value = try_audit!(
au,
u.first().ok_or(OperationError::Plugin).map(|v| v.clone())
)
u.first()
.ok_or(OperationError::Plugin)
.map(|v| (*v).clone())
);
v
}
None => Uuid::new_v4().to_hyphenated().to_string(),
None => Value::new_uuid(Uuid::new_v4()),
};
audit_log!(au, "Setting temporary UUID {} to entry", c_uuid);
let ava_uuid: Vec<String> = vec![c_uuid];
audit_log!(au, "Setting temporary UUID {:?} to entry", c_uuid);
let ava_uuid: Vec<Value> = vec![c_uuid];
entry.set_avas("uuid", ava_uuid);
audit_log!(au, "Temporary entry state: {:?}", entry);
}
// Now, every cand has a UUID - create a cand uuid set from it.
let mut cand_uuid: BTreeSet<&str> = BTreeSet::new();
let mut cand_uuid: BTreeSet<&Uuid> = BTreeSet::new();
// As we insert into the set, if a duplicate is found, return an error
// that a duplicate exists.
//
// Remember, we have to use the ava here, not the get_uuid types because
// we may not have filled in the uuid field yet.
for entry in cand.iter() {
let uuid_ref = entry
.get_ava("uuid")
let uuid_ref: &Uuid = entry
.get_ava_single("uuid")
.ok_or(OperationError::Plugin)?
.first()
.to_uuid()
.ok_or(OperationError::Plugin)?;
audit_log!(au, "Entry valid UUID: {:?}", entry);
match cand_uuid.insert(uuid_ref.as_str()) {
match cand_uuid.insert(uuid_ref) {
false => {
audit_log!(au, "uuid duplicate found in create set! {:?}", uuid_ref);
return Err(OperationError::Plugin);
@ -103,14 +117,20 @@ impl Plugin for Base {
}
}
// Setup UUIDS because lazy_static can't create a type valid for range.
let uuid_admin = UUID_ADMIN.clone();
let uuid_anonymous = UUID_ANONYMOUS.clone();
let uuid_does_not_exist = UUID_DOES_NOT_EXIST.clone();
// Check that the system-protected range is not in the cand_uuid, unless we are
// an internal operation.
if !ce.event.is_internal() {
// TODO: We can't lazy static this as you can't borrow the type down to what
// range and contains on btreeset need, but can we possibly make these staticly
// part of the struct somehow at init. rather than needing to parse a lot?
// The internal set is bounded by: UUID_ADMIN -> UUID_ANONYMOUS
// Sadly we need to allocate these to strings to make references, sigh.
// let uuid_admin: String = UUID_ADMIN.to_string();
// let uuid_anon: String = UUID_ANONYMOUS.to_string();
let overlap: usize = cand_uuid.range(UUID_ADMIN..UUID_ANONYMOUS).count();
let overlap: usize = cand_uuid.range(uuid_admin..uuid_anonymous).count();
if overlap != 0 {
audit_log!(
au,
@ -121,11 +141,11 @@ impl Plugin for Base {
}
}
if cand_uuid.contains(UUID_DOES_NOT_EXIST) {
if cand_uuid.contains(&uuid_does_not_exist) {
audit_log!(
au,
"uuid \"does not exist\" found in create set! {:?}",
UUID_DOES_NOT_EXIST
uuid_does_not_exist
);
return Err(OperationError::Plugin);
}
@ -133,7 +153,12 @@ impl Plugin for Base {
// Now from each element, generate a filter to search for all of them
//
// IMPORTANT: We don't exclude recycled or tombstones here!
let filt_in = filter_all!(FC::Or(cand_uuid.iter().map(|u| f_eq("uuid", u)).collect(),));
let filt_in = filter_all!(FC::Or(
cand_uuid
.iter()
.map(|u| FC::Eq("uuid", PartialValue::new_uuid(*u.clone())))
.collect(),
));
// If any results exist, fail as a duplicate UUID is present.
// TODO #69: Can we report which UUID exists? Probably yes, we do
@ -206,9 +231,9 @@ impl Plugin for Base {
// 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();
let uuid: &Uuid = e.get_uuid();
let filt = filter!(f_eq("uuid", uuid));
let filt = filter!(FC::Eq("uuid", PartialValue::new_uuid(uuid.clone())));
match qs.internal_search(au, filt) {
Ok(r) => {
if r.len() == 0 {
@ -252,6 +277,7 @@ mod tests {
use crate::modify::{Modify, ModifyList};
use crate::server::QueryServerTransaction;
use crate::server::QueryServerWriteTransaction;
use crate::value::{PartialValue, Value};
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
"valid": null,
@ -289,7 +315,7 @@ mod tests {
fn test_pre_create_no_uuid() {
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -300,8 +326,7 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e];
@ -312,7 +337,10 @@ mod tests {
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let cands = qs
.internal_search(au, filter!(f_eq("name", "testperson")))
.internal_search(
au,
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
)
.expect("Internal search failure");
let ue = cands.first().expect("No cand");
assert!(ue.attribute_pres("uuid"));
@ -325,7 +353,7 @@ mod tests {
fn test_pre_create_uuid_invalid() {
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -337,8 +365,7 @@ mod tests {
"uuid": ["xxxxxx"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];
@ -356,7 +383,7 @@ mod tests {
fn test_pre_create_uuid_empty() {
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -368,8 +395,7 @@ mod tests {
"uuid": []
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];
@ -387,7 +413,7 @@ mod tests {
fn test_pre_create_uuid_valid() {
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -399,8 +425,7 @@ mod tests {
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];
@ -411,10 +436,16 @@ mod tests {
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let cands = qs
.internal_search(au, filter!(f_eq("name", "testperson")))
.internal_search(
au,
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
)
.expect("Internal search failure");
let ue = cands.first().expect("No cand");
assert!(ue.attribute_equality("uuid", "79724141-3603-4060-b6bb-35c72772611d"));
assert!(ue.attribute_equality(
"uuid",
&PartialValue::new_uuids("79724141-3603-4060-b6bb-35c72772611d").unwrap()
));
}
);
}
@ -423,7 +454,7 @@ mod tests {
fn test_pre_create_uuid_valid_multi() {
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -436,7 +467,7 @@ mod tests {
}
}"#,
)
.expect("json parse failure");
;
let create = vec![e.clone()];
@ -459,7 +490,7 @@ mod tests {
// to ensure we always have a name space to draw from?
#[test]
fn test_pre_create_uuid_exist() {
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -471,8 +502,7 @@ mod tests {
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];
let preload = vec![e];
@ -491,7 +521,7 @@ mod tests {
// Test adding two entries with the same uuid
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -503,10 +533,9 @@ mod tests {
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
)
.expect("json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -518,8 +547,7 @@ mod tests {
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![ea, eb];
@ -536,7 +564,7 @@ mod tests {
#[test]
fn test_modify_uuid_present() {
// Add another uuid to a type
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -547,18 +575,17 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![ea];
run_modify_test!(
Err(OperationError::Plugin),
preload,
filter!(f_eq("name", "testgroup_a")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_a"))),
ModifyList::new_list(vec![Modify::Present(
"uuid".to_string(),
"f15a7219-1d15-44e3-a7b4-bec899c07788".to_string()
Value::from("f15a7219-1d15-44e3-a7b4-bec899c07788")
)]),
None,
|_, _| {}
@ -568,7 +595,7 @@ mod tests {
#[test]
fn test_modify_uuid_removed() {
// Test attempting to remove a uuid
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -579,18 +606,17 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![ea];
run_modify_test!(
Err(OperationError::Plugin),
preload,
filter!(f_eq("name", "testgroup_a")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_a"))),
ModifyList::new_list(vec![Modify::Removed(
"uuid".to_string(),
"f15a7219-1d15-44e3-a7b4-bec899c07788".to_string()
PartialValue::new_uuids("f15a7219-1d15-44e3-a7b4-bec899c07788").unwrap()
)]),
None,
|_, _| {}
@ -600,7 +626,7 @@ mod tests {
#[test]
fn test_modify_uuid_purged() {
// Test attempting to purge uuid
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -611,15 +637,14 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![ea];
run_modify_test!(
Err(OperationError::Plugin),
preload,
filter!(f_eq("name", "testgroup_a")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_a"))),
ModifyList::new_list(vec![Modify::Purged("uuid".to_string())]),
None,
|_, _| {}
@ -631,12 +656,11 @@ mod tests {
// Test an external create, it should fail.
// Testing internal create is not super needed, due to migrations at start
// up testing this every time we run :P
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let acp: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
let preload = vec![acp];
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -648,8 +672,7 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];
@ -667,7 +690,7 @@ mod tests {
// Test that internal create of "does not exist" will fail.
let preload = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -679,8 +702,7 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];

View file

@ -113,6 +113,7 @@ macro_rules! run_modify_test {
let mut qs_write = qs.write();
let r = qs_write.modify(&mut au_test, &me);
$check(&mut au_test, &qs_write);
debug!("{:?}", r);
assert!(r == $expect);
match r {
Ok(_) => {

View file

@ -18,29 +18,35 @@ use crate::modify::{Modify, ModifyList};
use crate::plugins::Plugin;
use crate::server::QueryServerTransaction;
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use uuid::Uuid;
lazy_static! {
static ref CLASS_GROUP: PartialValue = PartialValue::new_iutf8s("group");
}
pub struct MemberOf;
fn affected_uuids<'a, STATE>(
au: &mut AuditScope,
changed: Vec<&'a Entry<EntryValid, STATE>>,
) -> Vec<&'a String>
) -> Vec<&'a Uuid>
where
STATE: std::fmt::Debug,
{
// From the list of groups which were changed in this operation:
let changed_groups: Vec<_> = changed
.into_iter()
.filter(|e| e.attribute_value_pres("class", "group"))
.filter(|e| e.attribute_value_pres("class", &CLASS_GROUP))
.inspect(|e| {
audit_log!(au, "group reporting change: {:?}", e);
})
.collect();
// Now, build a map of all UUID's that will require updates as a result of this change
let mut affected_uuids: Vec<&String> = changed_groups
let mut affected_uuids: Vec<&Uuid> = changed_groups
.iter()
.filter_map(|e| {
// Only groups with member get collected up here.
@ -48,6 +54,7 @@ where
})
// Flatten the member's to the list.
.flatten()
.filter_map(|uv| uv.to_ref_uuid())
.collect();
// IDEA: promote groups to head of the affected_uuids set!
@ -65,7 +72,7 @@ where
fn apply_memberof(
au: &mut AuditScope,
qs: &mut QueryServerWriteTransaction,
affected_uuids: Vec<&String>,
affected_uuids: Vec<&Uuid>,
) -> Result<(), OperationError> {
audit_log!(au, " => entering apply_memberof");
audit_log!(au, "affected uuids -> {:?}", affected_uuids);
@ -93,24 +100,35 @@ fn apply_memberof(
au,
qs.internal_search(
au,
filter!(f_and!([f_eq("class", "group"), f_eq("member", a_uuid)]))
filter!(f_and!([
f_eq("class", CLASS_GROUP.clone()),
f_eq("member", PartialValue::new_refer_r(a_uuid))
]))
)
);
// get UUID of all groups + all memberof values
let mut dir_mo_set: Vec<_> = groups.iter().map(|g| g.get_uuid().clone()).collect();
let mut dir_mo_set: Vec<Value> = groups
.iter()
.map(|g| {
// These are turned into reference values.
Value::new_refer(g.get_uuid().clone())
})
.collect();
// No need to dedup this. Sorting could be of questionable
// value too though ...
dir_mo_set.sort();
dir_mo_set.dedup();
let mut mo_set: Vec<_> = groups
let mut mo_set: Vec<Value> = groups
.iter()
.map(|g| {
// TODO #61: This could be more effecient
let mut v = vec![g.get_uuid().clone()];
let mut v = vec![Value::new_refer(g.get_uuid().clone())];
match g.get_ava("memberof") {
Some(mos) => {
for mo in mos {
// This is cloning the existing reference values
v.push(mo.clone())
}
}
@ -133,7 +151,7 @@ fn apply_memberof(
// TODO #68: Could this affect replication? Or should the CL work out the
// true diff of the operation?
let mo_purge = vec![
Modify::Present("class".to_string(), "memberof".to_string()),
Modify::Present("class".to_string(), Value::new_class("memberof")),
Modify::Purged("memberof".to_string()),
Modify::Purged("directmemberof".to_string()),
];
@ -158,7 +176,11 @@ fn apply_memberof(
try_audit!(
au,
qs.internal_modify(au, filter!(f_eq("uuid", a_uuid)), modlist,)
qs.internal_modify(
au,
filter!(f_eq("uuid", PartialValue::new_uuid(a_uuid.clone()))),
modlist,
)
);
}
@ -194,15 +216,15 @@ impl Plugin for MemberOf {
_me: &ModifyEvent,
) -> Result<(), OperationError> {
// The condition here is critical - ONLY trigger on entries where changes occur!
let mut changed: Vec<&String> = pre_cand
let mut changed: Vec<&Uuid> = pre_cand
.iter()
.zip(cand.iter())
.filter(|(pre, post)| {
// This is the base case to break cycles in recursion!
(
// If it was a group, or will become a group.
post.attribute_value_pres("class", "group")
|| pre.attribute_value_pres("class", "group")
post.attribute_value_pres("class", &CLASS_GROUP)
|| pre.attribute_value_pres("class", &CLASS_GROUP)
)
// And the group has changed ...
&& pre != post
@ -216,13 +238,13 @@ impl Plugin for MemberOf {
})
.filter_map(|e| {
// Only groups with member get collected up here.
e.get_ava("member")
e.get_ava_reference_uuid("member")
})
// Flatten the uuid lists.
// Flatten the uuid reference lists.
.flatten()
.collect();
// Now tidy them up.
// Now tidy them up to reduce excesse searches/work.
changed.sort();
changed.dedup();
@ -290,7 +312,10 @@ impl Plugin for MemberOf {
// create new map
// let mo_set: BTreeMap<String, ()> = BTreeMap::new();
// searcch direct memberships of live groups.
let filt_in = filter!(f_eq("member", e.get_uuid().as_str()));
let filt_in = filter!(f_eq(
"member",
PartialValue::new_refer(e.get_uuid().clone())
));
let direct_memberof = match qs
.internal_search(au, filt_in)
@ -301,18 +326,16 @@ impl Plugin for MemberOf {
};
// for all direct -> add uuid to map
let d_groups_set: BTreeMap<&String, ()> =
direct_memberof.iter().map(|e| (e.get_uuid(), ())).collect();
let d_groups_set: BTreeSet<&Uuid> =
direct_memberof.iter().map(|e| e.get_uuid()).collect();
audit_log!(au, "Direct groups {:?} -> {:?}", e.get_uuid(), d_groups_set);
let dmos = match e.get_ava(&"directmemberof".to_string()) {
let dmos: Vec<&Uuid> = match e.get_ava_reference_uuid("directmemberof") {
// Avoid a reference issue to return empty set
Some(dmos) => dmos.clone(),
None => {
// No memberof, return empty set.
Vec::new()
}
Some(dmos) => dmos,
// No memberof, return empty set.
None => Vec::new(),
};
audit_log!(au, "DMO groups {:?} -> {:?}", e.get_uuid(), dmos);
@ -329,7 +352,7 @@ impl Plugin for MemberOf {
};
for mo_uuid in dmos {
if !d_groups_set.contains_key(&mo_uuid) {
if !d_groups_set.contains(mo_uuid) {
audit_log!(
au,
"Entry {:?}, MO {:?} not in direct groups",
@ -368,6 +391,12 @@ mod tests {
// use crate::error::OperationError;
use crate::modify::{Modify, ModifyList};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
static UUID_A: &'static str = "aaaaaaaa-f82e-4484-a407-181aa03bda5c";
static UUID_B: &'static str = "bbbbbbbb-2438-4384-9891-48f4c8172e9b";
static UUID_C: &'static str = "cccccccc-9b01-423f-9ba6-51aa4bbd5dd2";
static UUID_D: &'static str = "dddddddd-2ab3-48e3-938d-1b4754cd2984";
static EA: &'static str = r#"{
"valid": null,
@ -379,8 +408,6 @@ mod tests {
}
}"#;
static UUID_A: &'static str = "aaaaaaaa-f82e-4484-a407-181aa03bda5c";
static EB: &'static str = r#"{
"valid": null,
"state": null,
@ -391,8 +418,6 @@ mod tests {
}
}"#;
static UUID_B: &'static str = "bbbbbbbb-2438-4384-9891-48f4c8172e9b";
static EC: &'static str = r#"{
"valid": null,
"state": null,
@ -403,8 +428,6 @@ mod tests {
}
}"#;
static UUID_C: &'static str = "cccccccc-9b01-423f-9ba6-51aa4bbd5dd2";
static ED: &'static str = r#"{
"valid": null,
"state": null,
@ -415,8 +438,6 @@ mod tests {
}
}"#;
static UUID_D: &'static str = "dddddddd-2ab3-48e3-938d-1b4754cd2984";
macro_rules! assert_memberof_int {
(
$au:expr,
@ -426,7 +447,10 @@ mod tests {
$mo:expr,
$cand:expr
) => {{
let filt = filter!(f_and!([f_eq("uuid", $ea), f_eq($mo, $eb)]));
let filt = filter!(f_and!([
f_eq("uuid", PartialValue::new_uuids($ea).unwrap()),
f_eq($mo, PartialValue::new_refer_s($eb).unwrap())
]));
let cands = $qs
.internal_search($au, filt)
.expect("Internal search failure");
@ -482,13 +506,11 @@ mod tests {
#[test]
fn test_create_mo_single() {
// A -> B
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
ea.add_ava("member", UUID_B);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
let preload = Vec::new();
let create = vec![ea, eb];
@ -512,17 +534,14 @@ mod tests {
#[test]
fn test_create_mo_nested() {
// A -> B -> C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("member", UUID_C);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
let preload = Vec::new();
let create = vec![ea, eb, ec];
@ -566,18 +585,15 @@ mod tests {
fn test_create_mo_cycle() {
// A -> B -> C -
// ^-----------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("member", UUID_C);
ec.add_ava("member", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
let preload = Vec::new();
let create = vec![ea, eb, ec];
@ -621,25 +637,21 @@ mod tests {
// A -> B -> C --> D -
// ^-----------/ /
// |---------------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
let mut ed: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(ED).expect("Json parse failure");
let mut ed: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(ED);
ea.add_ava("member", UUID_B);
eb.add_ava("member", UUID_C);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("member", UUID_A);
ec.add_ava("member", UUID_D);
ec.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("member", &Value::new_refer_s(&UUID_D).unwrap());
ed.add_ava("member", UUID_A);
ed.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
let preload = Vec::new();
let create = vec![ea, eb, ec, ed];
@ -699,20 +711,18 @@ mod tests {
// A B
// Add member
// A -> B
let ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let preload = vec![ea, eb];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
UUID_B.to_string()
Value::new_refer_s(&UUID_B).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -732,25 +742,22 @@ mod tests {
// A B -> C
// Add member A -> B
// A -> B -> C
let ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
eb.add_ava("member", UUID_C);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
let preload = vec![ea, eb, ec];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
UUID_B.to_string()
Value::new_refer_s(&UUID_B).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -788,25 +795,22 @@ mod tests {
// A -> B C
// Add member B -> C
// A -> B -> C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
let preload = vec![ea, eb, ec];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_B)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_B).unwrap())),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
UUID_C.to_string()
Value::new_refer_s(&UUID_C).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -846,26 +850,23 @@ mod tests {
// Add member C -> A
// A -> B -> C -
// ^-----------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("member", UUID_C);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
let preload = vec![ea, eb, ec];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_C)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_C).unwrap())),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
UUID_A.to_string()
Value::new_refer_s(&UUID_A).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -909,30 +910,29 @@ mod tests {
// A -> B -> C --> D -
// ^-----------/ /
// |---------------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
let ed: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(ED).expect("Json parse failure");
let ed: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(ED);
ea.add_ava("member", UUID_B);
eb.add_ava("member", UUID_C);
ec.add_ava("member", UUID_D);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("member", &Value::new_refer_s(&UUID_D).unwrap());
let preload = vec![ea, eb, ec, ed];
run_modify_test!(
Ok(()),
preload,
filter!(f_or!([f_eq("uuid", UUID_C), f_eq("uuid", UUID_D),])),
filter!(f_or!([
f_eq("uuid", PartialValue::new_uuids(&UUID_C).unwrap()),
f_eq("uuid", PartialValue::new_uuids(&UUID_D).unwrap()),
])),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
UUID_A.to_string()
Value::new_refer_s(&UUID_A).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -986,23 +986,21 @@ mod tests {
// A -> B
// remove member A -> B
// A B
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
ea.add_ava("member", UUID_B);
eb.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
let preload = vec![ea, eb];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
ModifyList::new_list(vec![Modify::Removed(
"member".to_string(),
UUID_B.to_string()
PartialValue::new_refer_s(&UUID_B).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -1022,28 +1020,25 @@ mod tests {
// A -> B -> C
// Remove A -> B
// A B -> C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("memberof", UUID_A);
eb.add_ava("member", UUID_C);
ec.add_ava("memberof", UUID_B);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
let preload = vec![ea, eb, ec];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
ModifyList::new_list(vec![Modify::Removed(
"member".to_string(),
UUID_B.to_string()
PartialValue::new_refer_s(&UUID_B).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -1081,29 +1076,26 @@ mod tests {
// A -> B -> C
// Remove B -> C
// A -> B C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("memberof", UUID_A);
eb.add_ava("member", UUID_C);
ec.add_ava("memberof", UUID_B);
ec.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
let preload = vec![ea, eb, ec];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_B)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_B).unwrap())),
ModifyList::new_list(vec![Modify::Removed(
"member".to_string(),
UUID_C.to_string()
PartialValue::new_refer_s(&UUID_C).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -1142,38 +1134,35 @@ mod tests {
// ^-----------/
// Remove C -> A
// A -> B -> C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
ea.add_ava("memberof", UUID_C);
ea.add_ava("memberof", UUID_B);
ea.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("member", UUID_C);
eb.add_ava("memberof", UUID_C);
eb.add_ava("memberof", UUID_B);
eb.add_ava("memberof", UUID_A);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("member", UUID_A);
ec.add_ava("memberof", UUID_C);
ec.add_ava("memberof", UUID_B);
ec.add_ava("memberof", UUID_A);
ec.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
let preload = vec![ea, eb, ec];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_C)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_C).unwrap())),
ModifyList::new_list(vec![Modify::Removed(
"member".to_string(),
UUID_A.to_string()
PartialValue::new_refer_s(&UUID_A).unwrap()
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -1218,51 +1207,53 @@ mod tests {
// A -> B -> C D -
// ^ /
// |---------------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
let mut ed: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(ED).expect("Json parse failure");
let mut ed: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(ED);
ea.add_ava("member", UUID_B);
ea.add_ava("memberof", UUID_D);
ea.add_ava("memberof", UUID_C);
ea.add_ava("memberof", UUID_B);
ea.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("member", UUID_C);
eb.add_ava("memberof", UUID_D);
eb.add_ava("memberof", UUID_C);
eb.add_ava("memberof", UUID_B);
eb.add_ava("memberof", UUID_A);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("member", UUID_A);
ec.add_ava("member", UUID_D);
ec.add_ava("memberof", UUID_D);
ec.add_ava("memberof", UUID_C);
ec.add_ava("memberof", UUID_B);
ec.add_ava("memberof", UUID_A);
ec.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("member", &Value::new_refer_s(&UUID_D).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ed.add_ava("member", UUID_A);
ed.add_ava("memberof", UUID_D);
ed.add_ava("memberof", UUID_C);
ed.add_ava("memberof", UUID_B);
ed.add_ava("memberof", UUID_A);
ed.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
let preload = vec![ea, eb, ec, ed];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_C)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_C).unwrap())),
ModifyList::new_list(vec![
Modify::Removed("member".to_string(), UUID_A.to_string()),
Modify::Removed("member".to_string(), UUID_D.to_string()),
Modify::Removed(
"member".to_string(),
PartialValue::new_refer_s(&UUID_A).unwrap()
),
Modify::Removed(
"member".to_string(),
PartialValue::new_refer_s(&UUID_D).unwrap()
),
]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
@ -1314,20 +1305,18 @@ mod tests {
#[test]
fn test_delete_mo_simple() {
// X -> B
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
ea.add_ava("member", UUID_B);
eb.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
let preload = vec![ea, eb];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
// V-- this uuid is
@ -1344,27 +1333,24 @@ mod tests {
#[test]
fn test_delete_mo_nested_head() {
// X -> B -> C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("member", UUID_C);
ec.add_ava("memberof", UUID_A);
ec.add_ava("memberof", UUID_B);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
let preload = vec![ea, eb, ec];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
// V-- this uuid is
@ -1391,27 +1377,24 @@ mod tests {
#[test]
fn test_delete_mo_nested_branch() {
// A -> X -> C
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
eb.add_ava("memberof", UUID_A);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("member", UUID_C);
ec.add_ava("memberof", UUID_A);
ec.add_ava("memberof", UUID_B);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
let preload = vec![ea, eb, ec];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_B)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_B).unwrap())),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
// V-- this uuid is
@ -1439,35 +1422,32 @@ mod tests {
fn test_delete_mo_cycle() {
// X -> B -> C -
// ^-----------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
ea.add_ava("member", UUID_B);
ea.add_ava("memberof", UUID_A);
ea.add_ava("memberof", UUID_B);
ea.add_ava("memberof", UUID_C);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("member", UUID_C);
eb.add_ava("memberof", UUID_A);
eb.add_ava("memberof", UUID_B);
eb.add_ava("memberof", UUID_C);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("member", UUID_A);
ec.add_ava("memberof", UUID_A);
ec.add_ava("memberof", UUID_B);
ec.add_ava("memberof", UUID_C);
ec.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
let preload = vec![ea, eb, ec];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_A)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_A).unwrap())),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
// V-- this uuid is
@ -1496,48 +1476,44 @@ mod tests {
// A -> X -> C --> D -
// ^-----------/ /
// |---------------/
let mut ea: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EA).expect("Json parse failure");
let mut ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EA);
let mut eb: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EB).expect("Json parse failure");
let mut eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EB);
let mut ec: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(EC).expect("Json parse failure");
let mut ec: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(EC);
let mut ed: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(ED).expect("Json parse failure");
let mut ed: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(ED);
ea.add_ava("member", UUID_B);
ea.add_ava("memberof", UUID_A);
ea.add_ava("memberof", UUID_B);
ea.add_ava("memberof", UUID_C);
ea.add_ava("memberof", UUID_D);
ea.add_ava("member", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ea.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
eb.add_ava("member", UUID_C);
eb.add_ava("memberof", UUID_A);
eb.add_ava("memberof", UUID_B);
eb.add_ava("memberof", UUID_C);
eb.add_ava("memberof", UUID_D);
eb.add_ava("member", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
eb.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
ec.add_ava("member", UUID_A);
ec.add_ava("member", UUID_D);
ec.add_ava("memberof", UUID_A);
ec.add_ava("memberof", UUID_B);
ec.add_ava("memberof", UUID_C);
ec.add_ava("memberof", UUID_D);
ec.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("member", &Value::new_refer_s(&UUID_D).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ec.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
ed.add_ava("member", UUID_A);
ed.add_ava("memberof", UUID_A);
ed.add_ava("memberof", UUID_B);
ed.add_ava("memberof", UUID_C);
ed.add_ava("memberof", UUID_D);
ed.add_ava("member", &Value::new_refer_s(&UUID_A).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_A).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_B).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_C).unwrap());
ed.add_ava("memberof", &Value::new_refer_s(&UUID_D).unwrap());
let preload = vec![ea, eb, ec, ed];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("uuid", UUID_B)),
filter!(f_eq("uuid", PartialValue::new_uuids(&UUID_B).unwrap())),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
// V-- this uuid is

View file

@ -8,6 +8,7 @@ use crate::error::OperationError;
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
use crate::modify::Modify;
use crate::server::QueryServerWriteTransaction;
use crate::value::{PartialValue, Value};
use std::collections::HashSet;
pub struct Protected {}
@ -23,6 +24,8 @@ lazy_static! {
m.insert("may");
m
};
static ref PVCLASS_SYSTEM: PartialValue = PartialValue::new_class("system");
static ref VCLASS_SYSTEM: Value = Value::new_class("system");
}
impl Plugin for Protected {
@ -48,7 +51,7 @@ impl Plugin for Protected {
cand.iter().fold(Ok(()), |acc, cand| match acc {
Err(_) => acc,
Ok(_) => {
if cand.attribute_value_pres("class", "system") {
if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) {
Err(OperationError::SystemProtectedObject)
} else {
acc
@ -78,7 +81,8 @@ impl Plugin for Protected {
} else {
match m {
Modify::Present(a, v) => {
if a == "class" && v == "system" {
// TODO: Can we avoid this clone?
if a == "class" && v == &(VCLASS_SYSTEM.clone()) {
Err(OperationError::SystemProtectedObject)
} else {
Ok(())
@ -94,7 +98,7 @@ impl Plugin for Protected {
if acc {
acc
} else {
c.attribute_value_pres("class", "system")
c.attribute_value_pres("class", &PVCLASS_SYSTEM)
}
});
@ -141,7 +145,7 @@ impl Plugin for Protected {
cand.iter().fold(Ok(()), |acc, cand| match acc {
Err(_) => acc,
Ok(_) => {
if cand.attribute_value_pres("class", "system") {
if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) {
Err(OperationError::SystemProtectedObject)
} else {
acc
@ -156,6 +160,7 @@ mod tests {
use crate::constants::JSON_ADMIN_V1;
use crate::entry::{Entry, EntryInvalid, EntryNew};
use crate::error::OperationError;
use crate::value::{PartialValue, Value};
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
"valid": null,
@ -191,12 +196,11 @@ mod tests {
#[test]
fn test_pre_create_deny() {
// Test creating with class: system is rejected.
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let acp: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
let preload = vec![acp];
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -207,8 +211,7 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let create = vec![e.clone()];
@ -223,10 +226,9 @@ mod tests {
#[test]
fn test_pre_modify_system_deny() {
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let acp: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
// Test modify of class to a system is denied
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -237,16 +239,18 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![acp, e.clone()];
run_modify_test!(
Err(OperationError::SystemProtectedObject),
preload,
filter!(f_eq("name", "testperson")),
modlist!([m_purge("displayname"), m_pres("displayname", "system test"),]),
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
modlist!([
m_purge("displayname"),
m_pres("displayname", &Value::new_utf8s("system test")),
]),
Some(JSON_ADMIN_V1),
|_, _| {}
);
@ -254,10 +258,9 @@ mod tests {
#[test]
fn test_pre_modify_class_add_deny() {
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let acp: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
// Show that adding a system class is denied
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -268,16 +271,15 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![acp, e.clone()];
run_modify_test!(
Err(OperationError::SystemProtectedObject),
preload,
filter!(f_eq("name", "testperson")),
modlist!([m_pres("class", "system"),]),
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
modlist!([m_pres("class", &Value::new_class("system")),]),
Some(JSON_ADMIN_V1),
|_, _| {}
);
@ -285,10 +287,9 @@ mod tests {
#[test]
fn test_pre_modify_attr_must_may_allow() {
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let acp: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
// Show that adding a system class is denied
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -299,16 +300,18 @@ mod tests {
"description": ["Test Class"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![acp, e.clone()];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", "testclass")),
modlist!([m_pres("may", "name"), m_pres("must", "name"),]),
filter!(f_eq("name", PartialValue::new_iutf8s("testclass"))),
modlist!([
m_pres("may", &Value::new_iutf8s("name")),
m_pres("must", &Value::new_iutf8s("name")),
]),
Some(JSON_ADMIN_V1),
|_, _| {}
);
@ -316,10 +319,9 @@ mod tests {
#[test]
fn test_pre_delete_deny() {
let acp: Entry<EntryInvalid, EntryNew> =
serde_json::from_str(JSON_ADMIN_ALLOW_ALL).expect("json parse failure");
let acp: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
// Test deleting with class: system is rejected.
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -330,15 +332,14 @@ mod tests {
"displayname": ["testperson"]
}
}"#,
)
.expect("json parse failure");
);
let preload = vec![acp, e.clone()];
run_delete_test!(
Err(OperationError::SystemProtectedObject),
preload,
filter!(f_eq("name", "testperson")),
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
Some(JSON_ADMIN_V1),
|_, _| {}
);

View file

@ -9,7 +9,7 @@
// when that is written, as they *both* manipulate and alter entry reference
// data, so we should be careful not to step on each other.
use std::collections::HashMap;
use std::collections::BTreeSet;
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
@ -20,6 +20,8 @@ use crate::plugins::Plugin;
use crate::schema::SchemaTransaction;
use crate::server::QueryServerTransaction;
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
use uuid::Uuid;
// NOTE: This *must* be after base.rs!!!
@ -30,15 +32,18 @@ impl ReferentialIntegrity {
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
rtype: &String,
uuid: &String,
uuid_value: &Value,
) -> Result<(), OperationError> {
debug!("{:?}", uuid_value);
let uuid = try_audit!(au, uuid_value.to_ref_uuid().ok_or(OperationError::Plugin));
let mut au_qs = AuditScope::new("qs_exist");
let filt_in = filter!(f_eq("uuid", uuid));
// NOTE: This only checks LIVE entries (not using filter_all)
let filt_in = filter!(f_eq("uuid", PartialValue::new_uuid(uuid.clone())));
let r = qs.internal_exists(&mut au_qs, filt_in);
au.append_scope(au_qs);
let b = try_audit!(au, r);
// Is the reference in the qs?
// Is the reference in the result set?
if b {
Ok(())
} else {
@ -143,17 +148,18 @@ impl Plugin for ReferentialIntegrity {
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_uuid()).collect();
let uuids: Vec<&Uuid> = 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
// types too!
let filt = filter!(FC::Or(
let filt = filter_all!(FC::Or(
uuids
.iter()
.map(|u| ref_types
.values()
.map(move |r_type| f_eq(r_type.name.as_str(), u)))
.map(|u| ref_types.values().map(move |r_type| {
// For everything that references the uuid's in the deleted set.
f_eq(r_type.name.as_str(), PartialValue::new_refer(*u.clone()))
}))
.flatten()
.collect(),
));
@ -166,9 +172,9 @@ impl Plugin for ReferentialIntegrity {
uuids
.iter()
.map(|u| {
ref_types
.values()
.map(move |r_type| Modify::Removed(r_type.name.clone(), u.to_string()))
ref_types.values().map(move |r_type| {
Modify::Removed(r_type.name.clone(), PartialValue::new_refer(*u.clone()))
})
})
.flatten()
.collect(),
@ -197,9 +203,7 @@ impl Plugin for ReferentialIntegrity {
Err(e) => return vec![e],
};
let acu: Vec<&String> = all_cand.iter().map(|e| e.get_uuid()).collect();
let acu_map: HashMap<&String, ()> = acu.into_iter().map(|v| (v, ())).collect();
let acu_map: BTreeSet<&Uuid> = all_cand.iter().map(|e| e.get_uuid()).collect();
let schema = qs.get_schema();
let ref_types = schema.get_reference_types();
@ -214,8 +218,15 @@ impl Plugin for ReferentialIntegrity {
Some(vs) => {
// For each value in the set.
for v in vs {
if acu_map.get(v).is_none() {
res.push(Err(ConsistencyError::RefintNotUpheld(c.get_id())))
match v.to_ref_uuid() {
Some(vu) => {
if acu_map.get(vu).is_none() {
res.push(Err(ConsistencyError::RefintNotUpheld(c.get_id())))
}
}
None => res.push(Err(ConsistencyError::InvalidAttributeType(
"A non-value-ref type was found.",
))),
}
}
}
@ -236,11 +247,12 @@ mod tests {
use crate::error::OperationError;
use crate::modify::{Modify, ModifyList};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
// The create references a uuid that doesn't exist - reject
#[test]
fn test_create_uuid_reference_not_exist() {
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -251,8 +263,7 @@ mod tests {
"member": ["ca85168c-91b7-49a8-b7bb-a3d5bb40e97e"]
}
}"#,
)
.expect("Json parse failure");
);
let create = vec![e.clone()];
let preload = Vec::new();
@ -268,7 +279,7 @@ mod tests {
// The create references a uuid that does exist - validate
#[test]
fn test_create_uuid_reference_exist() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -279,10 +290,9 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -293,8 +303,7 @@ mod tests {
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea];
let create = vec![eb];
@ -306,7 +315,10 @@ mod tests {
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let cands = qs
.internal_search(au, filter!(f_eq("name", "testgroup_b")))
.internal_search(
au,
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
)
.expect("Internal search failure");
let _ue = cands.first().expect("No cand");
}
@ -318,7 +330,7 @@ mod tests {
fn test_create_uuid_reference_self() {
let preload: Vec<Entry<EntryInvalid, EntryNew>> = Vec::new();
let e: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -330,8 +342,7 @@ mod tests {
"member": ["8cef42bc-2cac-43e4-96b3-8f54561885ca"]
}
}"#,
)
.expect("Json parse failure");
);
let create = vec![e];
@ -342,7 +353,10 @@ mod tests {
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let cands = qs
.internal_search(au, filter!(f_eq("name", "testgroup")))
.internal_search(
au,
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup"))),
)
.expect("Internal search failure");
let _ue = cands.first().expect("No cand");
}
@ -352,7 +366,7 @@ mod tests {
// Modify references a different object - allow
#[test]
fn test_modify_uuid_reference_exist() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -363,10 +377,9 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -376,18 +389,17 @@ mod tests {
"description": ["testgroup"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea, eb];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", "testgroup_b")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
"d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string()
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
)]),
None,
|_, _| {}
@ -397,7 +409,7 @@ mod tests {
// Modify reference something that doesn't exist - must be rejected
#[test]
fn test_modify_uuid_reference_not_exist() {
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -407,18 +419,17 @@ mod tests {
"description": ["testgroup"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![eb];
run_modify_test!(
Err(OperationError::Plugin),
preload,
filter!(f_eq("name", "testgroup_b")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
"d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string()
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
)]),
None,
|_, _| {}
@ -428,7 +439,7 @@ mod tests {
// Modify removes the reference to an entry
#[test]
fn test_modify_remove_referee() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -439,10 +450,9 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -453,15 +463,14 @@ mod tests {
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea, eb];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", "testgroup_b")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
ModifyList::new_list(vec![Modify::Purged("member".to_string())]),
None,
|_, _| {}
@ -471,7 +480,7 @@ mod tests {
// Modify adds reference to self - allow
#[test]
fn test_modify_uuid_reference_self() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -482,18 +491,17 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", "testgroup_a")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_a"))),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
"d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string()
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
)]),
None,
|_, _| {}
@ -503,7 +511,7 @@ mod tests {
// Test that deleted entries can not be referenced
#[test]
fn test_modify_reference_deleted() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -514,10 +522,9 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -527,18 +534,17 @@ mod tests {
"description": ["testgroup"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea, eb];
run_modify_test!(
Err(OperationError::Plugin),
preload,
filter!(f_eq("name", "testgroup_b")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
ModifyList::new_list(vec![Modify::Present(
"member".to_string(),
"d2b496bd-8493-47b7-8142-f568b5cf47ee".to_string()
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
)]),
None,
|_, _| {}
@ -550,7 +556,7 @@ mod tests {
// This is the valid case, where the reference is MAY.
#[test]
fn test_delete_remove_referent_valid() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -561,10 +567,9 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -575,15 +580,14 @@ mod tests {
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea, eb];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("name", "testgroup_a")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_a"))),
None,
|_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {}
);
@ -598,7 +602,7 @@ mod tests {
// Delete of something that holds references.
#[test]
fn test_delete_remove_referee() {
let ea: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let ea: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -609,10 +613,9 @@ mod tests {
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -623,15 +626,14 @@ mod tests {
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![ea, eb];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("name", "testgroup_b")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
None,
|_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {}
);
@ -640,7 +642,7 @@ mod tests {
// Delete something that has a self reference.
#[test]
fn test_delete_remove_reference_self() {
let eb: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
let eb: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
@ -652,15 +654,14 @@ mod tests {
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
)
.expect("Json parse failure");
);
let preload = vec![eb];
run_delete_test!(
Ok(()),
preload,
filter!(f_eq("name", "testgroup_b")),
filter!(f_eq("name", PartialValue::new_iutf8s("testgroup_b"))),
None,
|_au: &mut AuditScope, _qs: &QueryServerWriteTransaction| {}
);

View file

@ -78,7 +78,7 @@ pub struct Entry {
pub attrs: BTreeMap<String, Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Filter {
// This is attr - value
Eq(String, String),

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

823
src/lib/value.rs Normal file
View file

@ -0,0 +1,823 @@
use crate::be::dbvalue::DbValueV1;
use crate::proto::v1::Filter as ProtoFilter;
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::str::FromStr;
use uuid::Uuid;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum IndexType {
EQUALITY,
PRESENCE,
SUBSTRING,
}
impl TryFrom<&str> for IndexType {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
let n_value = value.to_uppercase();
match n_value.as_str() {
"EQUALITY" => Ok(IndexType::EQUALITY),
"PRESENCE" => Ok(IndexType::PRESENCE),
"SUBSTRING" => Ok(IndexType::SUBSTRING),
_ => Err(()),
}
}
}
impl TryFrom<usize> for IndexType {
type Error = ();
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
0 => Ok(IndexType::EQUALITY),
1 => Ok(IndexType::PRESENCE),
2 => Ok(IndexType::SUBSTRING),
_ => Err(()),
}
}
}
impl IndexType {
pub fn to_string(&self) -> String {
String::from(match self {
IndexType::EQUALITY => "EQUALITY",
IndexType::PRESENCE => "PRESENCE",
IndexType::SUBSTRING => "SUBSTRING",
})
}
pub fn to_usize(&self) -> usize {
match self {
IndexType::EQUALITY => 0,
IndexType::PRESENCE => 1,
IndexType::SUBSTRING => 2,
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum SyntaxType {
// We need an insensitive string type too ...
// We also need to "self host" a syntax type, and index type
UTF8STRING,
UTF8STRING_INSENSITIVE,
UUID,
BOOLEAN,
SYNTAX_ID,
INDEX_ID,
REFERENCE_UUID,
JSON_FILTER,
}
impl TryFrom<&str> for SyntaxType {
type Error = ();
fn try_from(value: &str) -> Result<SyntaxType, Self::Error> {
let n_value = value.to_uppercase();
match n_value.as_str() {
"UTF8STRING" => Ok(SyntaxType::UTF8STRING),
"UTF8STRING_INSENSITIVE" => Ok(SyntaxType::UTF8STRING_INSENSITIVE),
"UUID" => Ok(SyntaxType::UUID),
"BOOLEAN" => Ok(SyntaxType::BOOLEAN),
"SYNTAX_ID" => Ok(SyntaxType::SYNTAX_ID),
"INDEX_ID" => Ok(SyntaxType::INDEX_ID),
"REFERENCE_UUID" => Ok(SyntaxType::REFERENCE_UUID),
"JSON_FILTER" => Ok(SyntaxType::JSON_FILTER),
_ => Err(()),
}
}
}
impl TryFrom<usize> for SyntaxType {
type Error = ();
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
0 => Ok(SyntaxType::UTF8STRING),
1 => Ok(SyntaxType::UTF8STRING_INSENSITIVE),
2 => Ok(SyntaxType::UUID),
3 => Ok(SyntaxType::BOOLEAN),
4 => Ok(SyntaxType::SYNTAX_ID),
5 => Ok(SyntaxType::INDEX_ID),
6 => Ok(SyntaxType::REFERENCE_UUID),
7 => Ok(SyntaxType::JSON_FILTER),
_ => Err(()),
}
}
}
impl SyntaxType {
pub fn to_string(&self) -> String {
String::from(match self {
SyntaxType::UTF8STRING => "UTF8STRING",
SyntaxType::UTF8STRING_INSENSITIVE => "UTF8STRING_INSENSITIVE",
SyntaxType::UUID => "UUID",
SyntaxType::BOOLEAN => "BOOLEAN",
SyntaxType::SYNTAX_ID => "SYNTAX_ID",
SyntaxType::INDEX_ID => "INDEX_ID",
SyntaxType::REFERENCE_UUID => "REFERENCE_UUID",
SyntaxType::JSON_FILTER => "JSON_FILTER",
})
}
pub fn to_usize(&self) -> usize {
match self {
SyntaxType::UTF8STRING => 0,
SyntaxType::UTF8STRING_INSENSITIVE => 1,
SyntaxType::UUID => 2,
SyntaxType::BOOLEAN => 3,
SyntaxType::SYNTAX_ID => 4,
SyntaxType::INDEX_ID => 5,
SyntaxType::REFERENCE_UUID => 6,
SyntaxType::JSON_FILTER => 7,
}
}
}
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
pub enum PartialValue {
Utf8(String),
Iutf8(String),
Uuid(Uuid),
Bool(bool),
Syntax(SyntaxType),
Index(IndexType),
Refer(Uuid),
// Does this make sense?
// TODO: We'll probably add tagging to this type for the partial matching
JsonFilt(ProtoFilter),
}
impl PartialValue {
pub fn new_utf8(s: String) -> Self {
PartialValue::Utf8(s)
}
pub fn new_utf8s(s: &str) -> Self {
PartialValue::Utf8(s.to_string())
}
pub fn is_utf8(&self) -> bool {
match self {
PartialValue::Utf8(_) => true,
_ => false,
}
}
pub fn new_iutf8s(s: &str) -> Self {
PartialValue::Iutf8(s.to_lowercase())
}
#[inline]
pub fn new_class(s: &str) -> Self {
PartialValue::new_iutf8s(s)
}
/*
#[inline]
pub fn new_attr(s: &str) -> Self {
PartialValue::new_iutf8s(s)
}
*/
pub fn is_iutf8(&self) -> bool {
match self {
PartialValue::Iutf8(_) => true,
_ => false,
}
}
pub fn new_bool(b: bool) -> Self {
PartialValue::Bool(b)
}
pub fn new_bools(s: &str) -> Option<Self> {
match bool::from_str(s) {
Ok(b) => Some(PartialValue::Bool(b)),
Err(_) => None,
}
}
pub fn is_bool(&self) -> bool {
match self {
PartialValue::Bool(_) => true,
_ => false,
}
}
pub fn new_uuid(u: Uuid) -> Self {
PartialValue::Uuid(u)
}
pub fn new_uuidr(u: &Uuid) -> Self {
PartialValue::Uuid(u.clone())
}
pub fn new_uuids(us: &str) -> Option<Self> {
match Uuid::parse_str(us) {
Ok(u) => Some(PartialValue::Uuid(u)),
Err(_) => None,
}
}
pub fn is_uuid(&self) -> bool {
match self {
PartialValue::Uuid(_) => true,
_ => false,
}
}
pub fn new_refer(u: Uuid) -> Self {
PartialValue::Refer(u)
}
pub fn new_refer_r(u: &Uuid) -> Self {
PartialValue::Refer(u.clone())
}
pub fn new_refer_s(us: &str) -> Option<Self> {
match Uuid::parse_str(us) {
Ok(u) => Some(PartialValue::Refer(u)),
Err(_) => None,
}
}
pub fn is_refer(&self) -> bool {
match self {
PartialValue::Refer(_) => true,
_ => false,
}
}
pub fn new_indexs(s: &str) -> Option<Self> {
let i = match IndexType::try_from(s) {
Ok(i) => i,
Err(_) => return None,
};
Some(PartialValue::Index(i))
}
pub fn is_index(&self) -> bool {
match self {
PartialValue::Index(_) => true,
_ => false,
}
}
pub fn new_syntaxs(s: &str) -> Option<Self> {
let i = match SyntaxType::try_from(s) {
Ok(i) => i,
Err(_) => return None,
};
Some(PartialValue::Syntax(i))
}
pub fn is_syntax(&self) -> bool {
match self {
PartialValue::Syntax(_) => true,
_ => false,
}
}
pub fn new_json_filter(s: &str) -> Option<Self> {
let pf: ProtoFilter = match serde_json::from_str(s) {
Ok(pf) => pf,
Err(_) => return None,
};
Some(PartialValue::JsonFilt(pf))
}
pub fn is_json_filter(&self) -> bool {
match self {
PartialValue::JsonFilt(_) => true,
_ => false,
}
}
pub fn to_str(&self) -> Option<&str> {
match self {
PartialValue::Utf8(s) => Some(s.as_str()),
PartialValue::Iutf8(s) => Some(s.as_str()),
_ => None,
}
}
pub fn to_str_unwrap(&self) -> &str {
self.to_str().expect("An invalid value was returned!!!")
}
pub fn contains(&self, s: &PartialValue) -> bool {
match (self, s) {
(PartialValue::Utf8(s1), PartialValue::Utf8(s2)) => s1.contains(s2),
(PartialValue::Iutf8(s1), PartialValue::Iutf8(s2)) => s1.contains(s2),
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct Value {
pv: PartialValue,
// Later we'll add extra data fields for different v types. They'll have to switch on
// pv somehow, so probably need optional or union?
}
// TODO: Impl display
// Need new_<type> -> Result<_, _>
// Need from_db_value
// Need to_db_value
// Need to_string for most types.
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value {
pv: PartialValue::Bool(b),
}
}
}
impl From<&bool> for Value {
fn from(b: &bool) -> Self {
Value {
pv: PartialValue::Bool(*b),
}
}
}
impl From<SyntaxType> for Value {
fn from(s: SyntaxType) -> Self {
Value {
pv: PartialValue::Syntax(s),
}
}
}
impl From<IndexType> for Value {
fn from(i: IndexType) -> Self {
Value {
pv: PartialValue::Index(i),
}
}
}
// Because these are potentially ambiguous, we limit them to tests where we can contain
// any....mistakes.
#[cfg(test)]
impl From<&str> for Value {
fn from(s: &str) -> Self {
// Fuzzy match for uuid's
// TODO: Will I regret this?
match Uuid::parse_str(s) {
Ok(u) => Value {
pv: PartialValue::Uuid(u),
},
Err(_) => Value {
pv: PartialValue::Utf8(s.to_string()),
},
}
}
}
#[cfg(test)]
impl From<&Uuid> for Value {
fn from(u: &Uuid) -> Self {
Value {
pv: PartialValue::Uuid(u.clone()),
}
}
}
#[cfg(test)]
impl From<Uuid> for Value {
fn from(u: Uuid) -> Self {
Value {
pv: PartialValue::Uuid(u),
}
}
}
impl Value {
// I get the feeling this will have a lot of matching ... sigh.
pub fn new_utf8(s: String) -> Self {
Value {
pv: PartialValue::new_utf8(s),
}
}
pub fn new_utf8s(s: &str) -> Self {
Value {
pv: PartialValue::new_utf8s(s),
}
}
pub fn is_utf8(&self) -> bool {
match self.pv {
PartialValue::Utf8(_) => true,
_ => false,
}
}
pub fn new_iutf8(s: String) -> Self {
Value {
pv: PartialValue::new_iutf8s(s.as_str()),
}
}
pub fn new_iutf8s(s: &str) -> Self {
Value {
pv: PartialValue::new_iutf8s(s),
}
}
pub fn is_insensitive_utf8(&self) -> bool {
match self.pv {
PartialValue::Iutf8(_) => true,
_ => false,
}
}
pub fn new_uuid(u: Uuid) -> Self {
Value {
pv: PartialValue::new_uuid(u),
}
}
pub fn new_uuids(s: &str) -> Option<Self> {
Some(Value {
pv: PartialValue::new_uuids(s)?,
})
}
pub fn new_uuidr(u: &Uuid) -> Self {
Value {
pv: PartialValue::new_uuidr(u),
}
}
// Is this correct? Should ref be seperate?
pub fn is_uuid(&self) -> bool {
match self.pv {
PartialValue::Uuid(_) => true,
_ => false,
}
}
pub fn new_class(s: &str) -> Self {
Value {
pv: PartialValue::new_iutf8s(s),
}
}
pub fn new_attr(s: &str) -> Self {
Value {
pv: PartialValue::new_iutf8s(s),
}
}
pub fn new_bool(b: bool) -> Self {
Value {
pv: PartialValue::new_bool(b),
}
}
pub fn new_bools(s: &str) -> Option<Self> {
Some(Value {
pv: PartialValue::new_bools(s)?,
})
}
#[inline]
pub fn is_bool(&self) -> bool {
match self.pv {
PartialValue::Bool(_) => true,
_ => false,
}
}
pub fn new_syntaxs(s: &str) -> Option<Self> {
Some(Value {
pv: PartialValue::new_syntaxs(s)?,
})
}
pub fn is_syntax(&self) -> bool {
match self.pv {
PartialValue::Syntax(_) => true,
_ => false,
}
}
pub fn new_indexs(s: &str) -> Option<Self> {
Some(Value {
pv: PartialValue::new_indexs(s)?,
})
}
pub fn is_index(&self) -> bool {
match self.pv {
PartialValue::Index(_) => true,
_ => false,
}
}
pub fn new_refer(u: Uuid) -> Self {
Value {
pv: PartialValue::new_refer(u),
}
}
pub fn new_refer_r(u: &Uuid) -> Self {
Value {
pv: PartialValue::new_refer_r(u),
}
}
pub fn new_refer_s(us: &str) -> Option<Self> {
Some(Value {
pv: PartialValue::new_refer_s(us)?,
})
}
pub fn is_refer(&self) -> bool {
match self.pv {
PartialValue::Refer(_) => true,
_ => false,
}
}
pub fn new_json_filter(s: &str) -> Option<Self> {
Some(Value {
pv: PartialValue::new_json_filter(s)?,
})
}
pub fn is_json_filter(&self) -> bool {
match self.pv {
PartialValue::JsonFilt(_) => true,
_ => false,
}
}
pub fn as_json_filter(&self) -> Option<&ProtoFilter> {
match &self.pv {
PartialValue::JsonFilt(f) => Some(f),
_ => None,
}
}
pub fn contains(&self, s: &PartialValue) -> bool {
self.pv.contains(s)
}
// Converters between DBRepr -> MemRepr. It's likely many of these
// will be just wrappers to our from str types.
// Keep this updated with DbValueV1 in be::dbvalue.
pub(crate) fn from_db_valuev1(v: DbValueV1) -> Result<Self, ()> {
// TODO: Should this actually take ownership? Or do we clone?
match v {
DbValueV1::U8(s) => Ok(Value {
pv: PartialValue::Utf8(s),
}),
DbValueV1::I8(s) => {
Ok(Value {
// TODO: Should we be lowercasing here? The dbv should be normalised
// already, but is there a risk of corruption/tampering if we don't touch this?
pv: PartialValue::Iutf8(s.to_lowercase()),
})
}
DbValueV1::UU(u) => Ok(Value {
pv: PartialValue::Uuid(u),
}),
DbValueV1::BO(b) => Ok(Value {
pv: PartialValue::Bool(b),
}),
DbValueV1::SY(us) => Ok(Value {
pv: PartialValue::Syntax(SyntaxType::try_from(us)?),
}),
DbValueV1::IN(us) => Ok(Value {
pv: PartialValue::Index(IndexType::try_from(us)?),
}),
DbValueV1::RF(u) => Ok(Value {
pv: PartialValue::Refer(u),
}),
DbValueV1::JF(s) => Ok(Value {
pv: match PartialValue::new_json_filter(s.as_str()) {
Some(pv) => pv,
None => return Err(()),
},
}),
}
}
pub(crate) fn to_db_valuev1(&self) -> DbValueV1 {
// TODO: Should this actually take ownership? Or do we clone?
match &self.pv {
PartialValue::Utf8(s) => DbValueV1::U8(s.clone()),
PartialValue::Iutf8(s) => DbValueV1::I8(s.clone()),
PartialValue::Uuid(u) => DbValueV1::UU(u.clone()),
PartialValue::Bool(b) => DbValueV1::BO(b.clone()),
PartialValue::Syntax(syn) => DbValueV1::SY(syn.to_usize()),
PartialValue::Index(it) => DbValueV1::IN(it.to_usize()),
PartialValue::Refer(u) => DbValueV1::RF(u.clone()),
PartialValue::JsonFilt(s) => DbValueV1::JF(
serde_json::to_string(s)
.expect("A json filter value was corrupted during run-time"),
),
}
}
/// Convert to a proto/public value that can be read and consumed.
pub(crate) fn to_proto_string_clone(&self) -> String {
match &self.pv {
PartialValue::Utf8(s) => s.clone(),
PartialValue::Iutf8(s) => s.clone(),
PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
PartialValue::Bool(b) => b.to_string(),
PartialValue::Syntax(syn) => syn.to_string(),
PartialValue::Index(it) => it.to_string(),
// TODO: These should be uuid_to_name in server
PartialValue::Refer(u) => u.to_hyphenated_ref().to_string(),
PartialValue::JsonFilt(s) => {
serde_json::to_string(s).expect("A json filter value was corrupted during run-time")
}
}
}
pub fn to_str(&self) -> Option<&str> {
match &self.pv {
PartialValue::Utf8(s) => Some(s.as_str()),
PartialValue::Iutf8(s) => Some(s.as_str()),
_ => None,
}
}
pub fn to_str_unwrap(&self) -> &str {
self.to_str().expect("An invalid value was returned!!!")
}
pub fn as_string(&self) -> Option<&String> {
match &self.pv {
PartialValue::Utf8(s) => Some(s),
PartialValue::Iutf8(s) => Some(s),
_ => None,
}
}
// We need a seperate to-ref_uuid to distinguish from normal uuids
// in refint plugin.
pub fn to_ref_uuid(&self) -> Option<&Uuid> {
match &self.pv {
PartialValue::Refer(u) => Some(&u),
_ => None,
}
}
pub fn to_uuid(&self) -> Option<&Uuid> {
match &self.pv {
PartialValue::Uuid(u) => Some(&u),
_ => None,
}
}
pub fn to_indextype(&self) -> Option<&IndexType> {
match &self.pv {
PartialValue::Index(i) => Some(&i),
_ => None,
}
}
pub fn to_syntaxtype(&self) -> Option<&SyntaxType> {
match &self.pv {
PartialValue::Syntax(s) => Some(&s),
_ => None,
}
}
pub fn to_bool(&self) -> Option<bool> {
match self.pv {
// *v is to invoke a copy, but this is cheap af
PartialValue::Bool(v) => Some(v),
_ => None,
}
}
pub fn to_partialvalue(&self) -> PartialValue {
// Match on self to become a partialvalue.
self.pv.clone()
}
pub fn validate(&self) -> bool {
// Validate that extra-data constraints on the type exist and are
// valid. IE json filter is really a filter, or cred types have supplemental
// data.
true
}
}
impl Borrow<PartialValue> for Value {
fn borrow(&self) -> &PartialValue {
&self.pv
}
}
#[cfg(test)]
mod tests {
use crate::value::*;
#[test]
fn test_value_index_tryfrom() {
let r1 = IndexType::try_from("EQUALITY");
assert_eq!(r1, Ok(IndexType::EQUALITY));
let r2 = IndexType::try_from("PRESENCE");
assert_eq!(r2, Ok(IndexType::PRESENCE));
let r3 = IndexType::try_from("SUBSTRING");
assert_eq!(r3, Ok(IndexType::SUBSTRING));
let r4 = IndexType::try_from("thaoeusaneuh");
assert_eq!(r4, Err(()));
}
#[test]
fn test_value_syntax_tryfrom() {
let r1 = SyntaxType::try_from("UTF8STRING");
assert_eq!(r1, Ok(SyntaxType::UTF8STRING));
let r2 = SyntaxType::try_from("UTF8STRING_INSENSITIVE");
assert_eq!(r2, Ok(SyntaxType::UTF8STRING_INSENSITIVE));
let r3 = SyntaxType::try_from("BOOLEAN");
assert_eq!(r3, Ok(SyntaxType::BOOLEAN));
let r4 = SyntaxType::try_from("SYNTAX_ID");
assert_eq!(r4, Ok(SyntaxType::SYNTAX_ID));
let r5 = SyntaxType::try_from("INDEX_ID");
assert_eq!(r5, Ok(SyntaxType::INDEX_ID));
let r6 = SyntaxType::try_from("zzzzantheou");
assert_eq!(r6, Err(()));
}
/*
#[test]
fn test_schema_syntax_json_filter() {
let sa = SchemaAttribute {
name: String::from("acp_receiver"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_ACP_RECEIVER)
.expect("unable to parse static uuid"),
description: String::from(
"Who the ACP applies to, constraining or allowing operations.",
),
multivalue: false,
index: vec![IndexType::EQUALITY, IndexType::SUBSTRING],
syntax: SyntaxType::JSON_FILTER,
};
// Outright wrong
let r1 = sa.validate_json_filter(&String::from("Whargarble lol not a filter"));
assert!(r1.is_err());
// Json error
let r2 = sa.validate_json_filter(&String::from(
"{\"And\":[{\"Eq\":[\"a\",\"a\"]},\"Self\",]}",
));
assert!(r2.is_err());
// Invalid keyword
let r3 = sa.validate_json_filter(&String::from(
"{\"And\":[{\"Nalf\":[\"a\",\"a\"]},\"Self\"]}",
));
assert!(r3.is_err());
// valid
let r4 = sa.validate_json_filter(&String::from("{\"Or\":[{\"Eq\":[\"a\",\"a\"]}]}"));
assert!(r4.is_ok());
// valid with self keyword
let r5 =
sa.validate_json_filter(&String::from("{\"And\":[{\"Eq\":[\"a\",\"a\"]},\"Self\"]}"));
assert!(r5.is_ok());
}
#[test]
fn test_schema_normalise_uuid() {
let sa = SchemaAttribute {
name: String::from("uuid"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_UUID).expect("unable to parse static uuid"),
description: String::from("The universal unique id of the object"),
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UUID,
};
let u1 = String::from("936DA01F9ABD4d9d80C702AF85C822A8");
let un1 = sa.normalise_value(&u1);
assert_eq!(un1, "936da01f-9abd-4d9d-80c7-02af85c822a8");
}
*/
}