mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-19 23:43:56 +02:00
20220908 dynamic groups (#1029)
This commit is contained in:
parent
bba5bd1a42
commit
d66edb829c
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -744,20 +744,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compiled-uuid"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18c9f404cbbb38aeaaa6a0cfe4039df817377926183d278098cdfe96dd015610"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"thiserror",
|
||||
"uuid 0.8.2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2123,7 +2110,6 @@ dependencies = [
|
|||
"base64urlsafedata",
|
||||
"chrono",
|
||||
"compact_jwt",
|
||||
"compiled-uuid",
|
||||
"concread",
|
||||
"criterion",
|
||||
"dyn-clone",
|
||||
|
@ -2164,7 +2150,7 @@ dependencies = [
|
|||
"url",
|
||||
"urlencoding",
|
||||
"users",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"validator",
|
||||
"webauthn-authenticator-rs",
|
||||
"webauthn-rs",
|
||||
|
@ -2185,7 +2171,7 @@ dependencies = [
|
|||
"toml",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"webauthn-rs-proto",
|
||||
]
|
||||
|
||||
|
@ -2201,7 +2187,7 @@ dependencies = [
|
|||
"time 0.2.27",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"webauthn-rs-proto",
|
||||
]
|
||||
|
||||
|
@ -2227,7 +2213,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"webauthn-authenticator-rs",
|
||||
"zxcvbn",
|
||||
]
|
||||
|
@ -2274,7 +2260,7 @@ dependencies = [
|
|||
"qrcode",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
|
@ -2809,7 +2795,7 @@ dependencies = [
|
|||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3499,7 +3485,7 @@ dependencies = [
|
|||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"webauthn-authenticator-rs",
|
||||
]
|
||||
|
||||
|
@ -4298,7 +4284,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4414,12 +4400,6 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.1.2"
|
||||
|
@ -4657,7 +4637,7 @@ dependencies = [
|
|||
"serde",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"webauthn-rs-core",
|
||||
]
|
||||
|
||||
|
@ -4680,7 +4660,7 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid",
|
||||
"webauthn-rs-proto",
|
||||
"x509-parser",
|
||||
]
|
||||
|
|
|
@ -21,7 +21,6 @@ base64 = "^0.13.0"
|
|||
base64urlsafedata = "0.1.0"
|
||||
chrono = "^0.4.20"
|
||||
compact_jwt = "^0.2.3"
|
||||
compiled-uuid = "0.1.2"
|
||||
concread = "^0.3.7"
|
||||
dyn-clone = "^1.0.9"
|
||||
fernet = { version = "^0.1.4", features = ["fernet_danger_timestamps"] }
|
||||
|
|
|
@ -1524,9 +1524,9 @@ mod tests {
|
|||
};
|
||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent};
|
||||
use crate::prelude::*;
|
||||
use compiled_uuid::uuid;
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
use uuid::uuid;
|
||||
|
||||
macro_rules! acp_from_entry_err {
|
||||
(
|
||||
|
@ -1548,8 +1548,8 @@ mod tests {
|
|||
$e:expr,
|
||||
$type:ty
|
||||
) => {{
|
||||
let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str($e);
|
||||
let ev1 = unsafe { e1.into_sealed_committed() };
|
||||
// let e1: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str($e);
|
||||
let ev1 = unsafe { $e.into_sealed_committed() };
|
||||
|
||||
let r1 = <$type>::try_from($qs, &ev1);
|
||||
assert!(r1.is_ok());
|
||||
|
@ -1611,19 +1611,23 @@ mod tests {
|
|||
// "\"Self\""
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
)
|
||||
),
|
||||
AccessControlProfile
|
||||
);
|
||||
})
|
||||
|
@ -1654,19 +1658,24 @@ mod tests {
|
|||
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile", "access_control_delete"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_delete")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
)
|
||||
),
|
||||
AccessControlDelete
|
||||
);
|
||||
})
|
||||
|
@ -1740,20 +1749,26 @@ mod tests {
|
|||
// All good!
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile", "access_control_search"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_search_attr": ["name", "class"]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_search")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
("acp_search_attr", Value::new_iutf8("name")),
|
||||
("acp_search_attr", Value::new_iutf8("class"))
|
||||
),
|
||||
AccessControlSearch
|
||||
);
|
||||
})
|
||||
|
@ -1788,40 +1803,50 @@ mod tests {
|
|||
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile", "access_control_modify"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_modify")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
)
|
||||
),
|
||||
AccessControlModify
|
||||
);
|
||||
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile", "access_control_modify"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_modify_removedattr": ["name"],
|
||||
"acp_modify_presentattr": ["name"],
|
||||
"acp_modify_class": ["object"]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_modify")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
("acp_modify_removedattr", Value::new_iutf8("name")),
|
||||
("acp_modify_presentattr", Value::new_iutf8("name")),
|
||||
("acp_modify_class", Value::new_iutf8("object"))
|
||||
),
|
||||
AccessControlModify
|
||||
);
|
||||
})
|
||||
|
@ -1855,39 +1880,49 @@ mod tests {
|
|||
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile", "access_control_create"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_create")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
)
|
||||
),
|
||||
AccessControlCreate
|
||||
);
|
||||
|
||||
acp_from_entry_ok!(
|
||||
&mut qs_write,
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["object", "access_control_profile", "access_control_create"],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_create_class": ["object"],
|
||||
"acp_create_attr": ["name"]
|
||||
}
|
||||
}"#,
|
||||
entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_create")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
("acp_create_attr", Value::new_iutf8("name")),
|
||||
("acp_create_class", Value::new_iutf8("object"))
|
||||
),
|
||||
AccessControlCreate
|
||||
);
|
||||
})
|
||||
|
@ -1902,36 +1937,37 @@ mod tests {
|
|||
// over a single scope.
|
||||
let mut qs_write = qs.write(duration_from_epoch_now());
|
||||
|
||||
let e: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"access_control_profile",
|
||||
"access_control_create",
|
||||
"access_control_delete",
|
||||
"access_control_modify",
|
||||
"access_control_search"
|
||||
],
|
||||
"name": ["acp_valid"],
|
||||
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"eq\":[\"name\",\"a\"]}"
|
||||
],
|
||||
"acp_search_attr": ["name"],
|
||||
"acp_create_class": ["object"],
|
||||
"acp_create_attr": ["name"],
|
||||
"acp_modify_removedattr": ["name"],
|
||||
"acp_modify_presentattr": ["name"],
|
||||
"acp_modify_class": ["object"]
|
||||
}
|
||||
}"#;
|
||||
let e = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("access_control_profile")),
|
||||
("class", Value::new_class("access_control_create")),
|
||||
("class", Value::new_class("access_control_delete")),
|
||||
("class", Value::new_class("access_control_modify")),
|
||||
("class", Value::new_class("access_control_search")),
|
||||
("name", Value::new_iname("acp_valid")),
|
||||
(
|
||||
"uuid",
|
||||
Value::new_uuids("cc8e95b4-c24f-4d68-ba54-8bed76f63930").expect("uuid")
|
||||
),
|
||||
(
|
||||
"acp_receiver",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(
|
||||
"acp_targetscope",
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
("acp_search_attr", Value::new_iutf8("name")),
|
||||
("acp_create_class", Value::new_iutf8("class")),
|
||||
("acp_create_attr", Value::new_iutf8("name")),
|
||||
("acp_modify_removedattr", Value::new_iutf8("name")),
|
||||
("acp_modify_presentattr", Value::new_iutf8("name")),
|
||||
("acp_modify_class", Value::new_iutf8("object"))
|
||||
);
|
||||
|
||||
acp_from_entry_ok!(&mut qs_write, e, AccessControlCreate);
|
||||
acp_from_entry_ok!(&mut qs_write, e, AccessControlDelete);
|
||||
acp_from_entry_ok!(&mut qs_write, e, AccessControlModify);
|
||||
acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlCreate);
|
||||
acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlDelete);
|
||||
acp_from_entry_ok!(&mut qs_write, e.clone(), AccessControlModify);
|
||||
acp_from_entry_ok!(&mut qs_write, e, AccessControlSearch);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -415,6 +415,18 @@ pub const JSON_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV: &str = r#"{
|
|||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_IDM_ALL_PERSONS: &str = r#"{
|
||||
"attrs": {
|
||||
"class": ["dyngroup", "group", "object"],
|
||||
"name": ["idm_all_persons"],
|
||||
"uuid": ["00000000-0000-0000-0000-000000000035"],
|
||||
"description": ["Builtin IDM dynamic group containing all persons"],
|
||||
"dyngroup_filter": [
|
||||
"{\"eq\":[\"class\",\"person\"]}"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
/// This must be the last group to init to include the UUID of the other high priv groups.
|
||||
pub const JSON_IDM_HIGH_PRIVILEGE_V1: &str = r#"{
|
||||
"attrs": {
|
||||
|
|
|
@ -990,6 +990,34 @@ pub const JSON_SCHEMA_ATTR_DEVICEKEYS: &str = r#"{
|
|||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_ATTR_DYNGROUP_FILTER: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"system",
|
||||
"attributetype"
|
||||
],
|
||||
"description": [
|
||||
"A filter describing the set of entries to add to a dynamic group"
|
||||
],
|
||||
"unique": [
|
||||
"false"
|
||||
],
|
||||
"multivalue": [
|
||||
"false"
|
||||
],
|
||||
"attributename": [
|
||||
"dyngroup_filter"
|
||||
],
|
||||
"syntax": [
|
||||
"JSON_FILTER"
|
||||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000108"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
// === classes ===
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_PERSON: &str = r#"
|
||||
|
@ -1078,6 +1106,33 @@ pub const JSON_SCHEMA_CLASS_GROUP: &str = r#"
|
|||
}
|
||||
"#;
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_DYNGROUP: &str = r#"
|
||||
{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"system",
|
||||
"classtype"
|
||||
],
|
||||
"description": [
|
||||
"Object representation of a dynamic group"
|
||||
],
|
||||
"classname": [
|
||||
"dyngroup"
|
||||
],
|
||||
"systemmust": [
|
||||
"dyngroup_filter"
|
||||
],
|
||||
"systemsupplements": [
|
||||
"group"
|
||||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000107"
|
||||
]
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_ACCOUNT: &str = r#"
|
||||
{
|
||||
"attrs": {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use compiled_uuid::uuid;
|
||||
use uuid::Uuid;
|
||||
use uuid::{uuid, Uuid};
|
||||
|
||||
// Built in group and account ranges.
|
||||
pub const STR_UUID_ADMIN: &str = "00000000-0000-0000-0000-000000000000";
|
||||
|
@ -47,6 +46,8 @@ pub const _UUID_IDM_PEOPLE_SELF_WRITE_MAIL_PRIV: Uuid =
|
|||
pub const _UUID_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-000000000034");
|
||||
|
||||
pub const UUID_IDM_ALL_PERSONS: Uuid = uuid!("00000000-0000-0000-0000-000000000035");
|
||||
|
||||
//
|
||||
pub const _UUID_IDM_HIGH_PRIVILEGE: Uuid = uuid!("00000000-0000-0000-0000-000000001000");
|
||||
|
||||
|
@ -181,6 +182,8 @@ pub const UUID_SCHEMA_ATTR_SYSTEMEXCLUDES: Uuid = uuid!("00000000-0000-0000-0000
|
|||
pub const UUID_SCHEMA_ATTR_EXCLUDES: Uuid = uuid!("00000000-0000-0000-0000-ffff00000104");
|
||||
pub const UUID_SCHEMA_ATTR_SCOPE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000105");
|
||||
pub const UUID_SCHEMA_CLASS_SERVICE_ACCOUNT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000106");
|
||||
pub const _UUID_SCHEMA_CLASS_DYNGROUP: Uuid = uuid!("00000000-0000-0000-0000-ffff00000107");
|
||||
pub const _UUID_SCHEMA_ATTR_DYNGROUP_FILTER: Uuid = uuid!("00000000-0000-0000-0000-ffff00000108");
|
||||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
|
|
|
@ -2277,7 +2277,7 @@ where
|
|||
|
||||
/// Remove an attribute-value pair from this entry. If the ava doesn't exist, we
|
||||
/// don't do anything else since we are asserting the abscence of a value.
|
||||
fn remove_ava(&mut self, attr: &str, value: &PartialValue) {
|
||||
pub(crate) fn remove_ava(&mut self, attr: &str, value: &PartialValue) {
|
||||
self.valid
|
||||
.eclog
|
||||
.remove_ava_iter(&self.valid.cid, attr, std::iter::once(value.clone()));
|
||||
|
|
|
@ -1488,8 +1488,8 @@ mod tests {
|
|||
use webauthn_authenticator_rs::{softpasskey::SoftPasskey, WebauthnAuthenticator};
|
||||
|
||||
use crate::idm::AuthState;
|
||||
use compiled_uuid::uuid;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthMech, CredentialDetailType};
|
||||
use uuid::uuid;
|
||||
|
||||
use async_std::task;
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
//! The Kanidmd server library. This implements all of the internal components of the server
|
||||
//! which is used to process authentication, store identities and enforce access controls.
|
||||
|
||||
#![deny(warnings)]
|
||||
//#![deny(warnings)]
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
#![warn(unused_extern_crates)]
|
||||
#![deny(clippy::todo)]
|
||||
|
@ -64,7 +65,7 @@ pub mod config;
|
|||
/// help make imports cleaner.
|
||||
pub mod prelude {
|
||||
pub use crate::utils::duration_from_epoch_now;
|
||||
pub use kanidm_proto::v1::OperationError;
|
||||
pub use kanidm_proto::v1::{ConsistencyError, OperationError};
|
||||
pub use smartstring::alias::String as AttrString;
|
||||
pub use url::Url;
|
||||
pub use uuid::Uuid;
|
||||
|
|
|
@ -207,6 +207,7 @@ impl ModifyList<ModifyInvalid> {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) unsafe fn into_valid(self) -> ModifyList<ModifyValid> {
|
||||
ModifyList {
|
||||
valid: ModifyValid,
|
||||
|
|
|
@ -113,6 +113,11 @@ impl Plugin for AttrUnique {
|
|||
"plugin_attrunique"
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "attrunique_pre_create_transform",
|
||||
skip(qs, cand, _ce)
|
||||
)]
|
||||
fn pre_create_transform(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||
|
@ -130,6 +135,7 @@ impl Plugin for AttrUnique {
|
|||
r
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "attrunique_pre_modify", skip(qs, cand, _me))]
|
||||
fn pre_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
@ -147,6 +153,7 @@ impl Plugin for AttrUnique {
|
|||
r
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "attrunique_verify", skip(qs))]
|
||||
fn verify(qs: &QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
||||
// Only check live entries, not recycled.
|
||||
let filt_in = filter!(f_pres("class"));
|
||||
|
|
|
@ -28,14 +28,12 @@ impl Plugin for Base {
|
|||
fn id() -> &'static str {
|
||||
"plugin_base"
|
||||
}
|
||||
// Need to be given the backend(for testing ease)
|
||||
// audit
|
||||
// the mut set of entries to create
|
||||
// the create event itself (immutable, for checking originals)
|
||||
// contains who is creating them
|
||||
// the schema of the running instance
|
||||
|
||||
// TODO: Can this be improved?
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "base_pre_create_transform",
|
||||
skip(qs, cand, ce)
|
||||
)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn pre_create_transform(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
|
@ -169,6 +167,7 @@ impl Plugin for Base {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "base_pre_modify", skip(_qs, _cand, me))]
|
||||
fn pre_modify(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
@ -188,6 +187,7 @@ impl Plugin for Base {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "base_verify", skip(qs))]
|
||||
fn verify(qs: &QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
||||
// Search for class = *
|
||||
let entries = match qs.internal_search(filter!(f_pres("class"))) {
|
||||
|
|
|
@ -25,6 +25,11 @@ impl Plugin for Domain {
|
|||
"plugin_domain"
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "domain_pre_create_transform",
|
||||
skip(qs, cand, _ce)
|
||||
)]
|
||||
fn pre_create_transform(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||
|
@ -76,6 +81,7 @@ impl Plugin for Domain {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "domain_pre_modify", skip(qs, cand, _me))]
|
||||
fn pre_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
|
925
kanidmd/idm/src/plugins/dyngroup.rs
Normal file
925
kanidmd/idm/src/plugins/dyngroup.rs
Normal file
|
@ -0,0 +1,925 @@
|
|||
use crate::event::{CreateEvent, ModifyEvent};
|
||||
use crate::filter::FilterInvalid;
|
||||
use crate::prelude::*;
|
||||
use kanidm_proto::v1::Filter as ProtoFilter;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
lazy_static! {
|
||||
static ref CLASS_DYNGROUP: PartialValue = PartialValue::new_class("dyngroup");
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynGroupCache {
|
||||
insts: BTreeMap<Uuid, Filter<FilterInvalid>>,
|
||||
}
|
||||
|
||||
impl Default for DynGroupCache {
|
||||
fn default() -> Self {
|
||||
DynGroupCache {
|
||||
insts: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynGroup;
|
||||
|
||||
impl DynGroup {
|
||||
fn apply_dyngroup_change(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
ident: &Identity,
|
||||
pre_candidates: &mut Vec<Arc<EntrySealedCommitted>>,
|
||||
candidates: &mut Vec<EntryInvalidCommitted>,
|
||||
affected_uuids: &mut Vec<Uuid>,
|
||||
expect: bool,
|
||||
ident_internal: &Identity,
|
||||
dyn_groups: &mut DynGroupCache,
|
||||
n_dyn_groups: &[&Entry<EntrySealed, EntryCommitted>],
|
||||
) -> Result<(), OperationError> {
|
||||
if !ident.is_internal() {
|
||||
// It should be impossible to trigger this right now due to protected plugin.
|
||||
error!("It is currently an error to create a dynamic group");
|
||||
return Err(OperationError::SystemProtectedObject);
|
||||
}
|
||||
|
||||
// Search all the new groups first.
|
||||
let filt = filter!(FC::Or(
|
||||
n_dyn_groups
|
||||
.into_iter()
|
||||
.map(|e| f_eq("uuid", PartialValue::new_uuid(e.get_uuid())))
|
||||
.collect()
|
||||
));
|
||||
let work_set = qs.internal_search_writeable(&filt)?;
|
||||
|
||||
// Go through them all and update the new groups.
|
||||
for (pre, mut nd_group) in work_set.into_iter() {
|
||||
let scope_f: ProtoFilter = nd_group
|
||||
.get_ava_single_protofilter("dyngroup_filter")
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
admin_error!("Missing dyngroup_filter");
|
||||
OperationError::InvalidEntryState
|
||||
})?;
|
||||
|
||||
let scope_i = Filter::from_rw(ident_internal, &scope_f, qs).map_err(|e| {
|
||||
admin_error!("dyngroup_filter validation failed {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let uuid = pre.get_uuid();
|
||||
// Add our uuid as affected.
|
||||
affected_uuids.push(uuid);
|
||||
|
||||
// Apply the filter and get all the uuids.
|
||||
let entries = qs.internal_search(scope_i.clone()).map_err(|e| {
|
||||
admin_error!("internal search failure -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let members = ValueSetRefer::from_iter(entries.iter().map(|e| e.get_uuid()));
|
||||
|
||||
if let Some(uuid_iter) = members.as_ref().and_then(|a| a.as_ref_uuid_iter()) {
|
||||
affected_uuids.extend(uuid_iter);
|
||||
}
|
||||
|
||||
if let Some(members) = members {
|
||||
// Only set something if there is actually something to do!
|
||||
nd_group.set_ava_set("member", members);
|
||||
// push the entries to pre/cand
|
||||
} else {
|
||||
nd_group.purge_ava("member");
|
||||
}
|
||||
|
||||
pre_candidates.push(pre);
|
||||
candidates.push(nd_group);
|
||||
|
||||
// Insert to our new instances
|
||||
if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
|
||||
admin_error!("dyngroup cache uuid conflict {}", uuid);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "dyngroup_reload", skip(qs))]
|
||||
pub fn reload(qs: &QueryServerWriteTransaction) -> Result<(), OperationError> {
|
||||
let ident_internal = Identity::from_internal();
|
||||
// Internal search all our definitions.
|
||||
let filt = filter!(f_eq("class", PartialValue::new_class("dyngroup")));
|
||||
let entries = qs.internal_search(filt).map_err(|e| {
|
||||
admin_error!("internal search failure -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let dyn_groups = qs.get_dyngroup_cache();
|
||||
|
||||
dyn_groups.insts.clear();
|
||||
|
||||
for nd_group in entries.into_iter() {
|
||||
let scope_f: ProtoFilter = nd_group
|
||||
.get_ava_single_protofilter("dyngroup_filter")
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
admin_error!("Missing dyngroup_filter");
|
||||
OperationError::InvalidEntryState
|
||||
})?;
|
||||
|
||||
let scope_i = Filter::from_rw(&ident_internal, &scope_f, qs).map_err(|e| {
|
||||
admin_error!("dyngroup_filter validation failed {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let uuid = nd_group.get_uuid();
|
||||
|
||||
if dyn_groups.insts.insert(uuid, scope_i).is_some() {
|
||||
admin_error!("dyngroup cache uuid conflict {}", uuid);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "dyngroup_post_create", skip(qs, cand, ce))]
|
||||
pub fn post_create(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
ce: &CreateEvent,
|
||||
) -> Result<Vec<Uuid>, OperationError> {
|
||||
let mut affected_uuids = Vec::with_capacity(cand.len());
|
||||
|
||||
let ident_internal = Identity::from_internal();
|
||||
|
||||
let (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand
|
||||
.iter()
|
||||
.partition(|entry| entry.attribute_equality("class", &CLASS_DYNGROUP));
|
||||
|
||||
let dyn_groups = qs.get_dyngroup_cache();
|
||||
|
||||
// For any other entries, check if they SHOULD trigger
|
||||
// a dyn group inclusion. We do this FIRST because the new
|
||||
// dyn groups will see the created entries on an internal search
|
||||
// so we don't need to reference them.
|
||||
|
||||
//
|
||||
let resolve_filter_cache = qs.get_resolve_filter_cache();
|
||||
|
||||
let mut pre_candidates = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
|
||||
let mut candidates = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
|
||||
|
||||
trace!(?dyn_groups.insts);
|
||||
|
||||
for (dg_uuid, dg_filter) in dyn_groups.insts.iter() {
|
||||
let dg_filter_valid = dg_filter
|
||||
.validate(qs.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)
|
||||
.and_then(|f| f.resolve(&ident_internal, None, Some(resolve_filter_cache)))?;
|
||||
|
||||
let matches: Vec<_> = entries
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
if e.entry_match_no_index(&dg_filter_valid) {
|
||||
Some(e.get_uuid())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !matches.is_empty() {
|
||||
let filt = filter!(f_eq("uuid", PartialValue::new_uuid(*dg_uuid)));
|
||||
let mut work_set = qs.internal_search_writeable(&filt)?;
|
||||
|
||||
if let Some((pre, mut d_group)) = work_set.pop() {
|
||||
matches
|
||||
.iter()
|
||||
.copied()
|
||||
.for_each(|u| d_group.add_ava("member", Value::new_refer(u)));
|
||||
|
||||
affected_uuids.extend(matches.into_iter());
|
||||
affected_uuids.push(*dg_uuid);
|
||||
|
||||
pre_candidates.push(pre);
|
||||
candidates.push(d_group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we created any dyn groups, populate them now.
|
||||
// if the event is not internal, reject (for now)
|
||||
|
||||
if !n_dyn_groups.is_empty() {
|
||||
trace!("considering new dyngroups");
|
||||
Self::apply_dyngroup_change(
|
||||
qs,
|
||||
&ce.ident,
|
||||
&mut pre_candidates,
|
||||
&mut candidates,
|
||||
&mut affected_uuids,
|
||||
false,
|
||||
&ident_internal,
|
||||
dyn_groups,
|
||||
n_dyn_groups.as_slice(),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Write back the new changes.
|
||||
debug_assert!(pre_candidates.len() == candidates.len());
|
||||
// Write this stripe if populated.
|
||||
if !pre_candidates.is_empty() {
|
||||
qs.internal_batch_modify(pre_candidates, candidates)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to commit dyngroup set {:?}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(affected_uuids)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "memberof_post_modify",
|
||||
skip(qs, pre_cand, cand, me)
|
||||
)]
|
||||
pub fn post_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
me: &ModifyEvent,
|
||||
) -> Result<Vec<Uuid>, OperationError> {
|
||||
let mut affected_uuids = Vec::with_capacity(cand.len());
|
||||
|
||||
let ident_internal = Identity::from_internal();
|
||||
let resolve_filter_cache = qs.get_resolve_filter_cache();
|
||||
|
||||
// Probably should be filter here instead.
|
||||
let (_, pre_entries): (Vec<&Arc<Entry<_, _>>>, Vec<_>) = pre_cand
|
||||
.iter()
|
||||
.partition(|entry| entry.attribute_equality("class", &CLASS_DYNGROUP));
|
||||
|
||||
let (n_dyn_groups, post_entries): (Vec<&Entry<_, _>>, Vec<_>) = cand
|
||||
.iter()
|
||||
.partition(|entry| entry.attribute_equality("class", &CLASS_DYNGROUP));
|
||||
|
||||
let dyn_groups = qs.get_dyngroup_cache();
|
||||
|
||||
let mut pre_candidates = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
|
||||
let mut candidates = Vec::with_capacity(dyn_groups.insts.len() + cand.len());
|
||||
|
||||
// If we modified a dyngroups member or filter, re-trigger it here.
|
||||
// if the event is not internal, reject (for now)
|
||||
// We do this *first* so that we don't accidentally include/exclude anything that
|
||||
// changed in this op.
|
||||
|
||||
if !n_dyn_groups.is_empty() {
|
||||
trace!("considering modified dyngroups");
|
||||
Self::apply_dyngroup_change(
|
||||
qs,
|
||||
&me.ident,
|
||||
&mut pre_candidates,
|
||||
&mut candidates,
|
||||
&mut affected_uuids,
|
||||
true,
|
||||
&ident_internal,
|
||||
dyn_groups,
|
||||
n_dyn_groups.as_slice(),
|
||||
)?;
|
||||
}
|
||||
|
||||
// If we modified anything else, check if a dyngroup is affected by it's change
|
||||
// if it was a member.
|
||||
|
||||
trace!(?dyn_groups.insts);
|
||||
|
||||
for (dg_uuid, dg_filter) in dyn_groups.insts.iter() {
|
||||
let dg_filter_valid = dg_filter
|
||||
.validate(qs.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)
|
||||
.and_then(|f| f.resolve(&ident_internal, None, Some(resolve_filter_cache)))?;
|
||||
|
||||
let matches: Vec<_> = pre_entries
|
||||
.iter()
|
||||
.zip(post_entries.iter())
|
||||
.filter_map(|(pre, post)| {
|
||||
let pre_t = pre.entry_match_no_index(&dg_filter_valid);
|
||||
let post_t = post.entry_match_no_index(&dg_filter_valid);
|
||||
|
||||
if pre_t && !post_t {
|
||||
Some(Err(post.get_uuid()))
|
||||
} else if !pre_t && post_t {
|
||||
Some(Ok(post.get_uuid()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !matches.is_empty() {
|
||||
let filt = filter!(f_eq("uuid", PartialValue::new_uuid(*dg_uuid)));
|
||||
let mut work_set = qs.internal_search_writeable(&filt)?;
|
||||
|
||||
if let Some((pre, mut d_group)) = work_set.pop() {
|
||||
matches.iter().copied().for_each(|choice| match choice {
|
||||
Ok(u) => d_group.add_ava("member", Value::new_refer(u)),
|
||||
Err(u) => d_group.remove_ava("member", &PartialValue::new_refer(u)),
|
||||
});
|
||||
|
||||
affected_uuids.extend(matches.into_iter().map(|choice| match choice {
|
||||
Ok(u) => u,
|
||||
Err(u) => u,
|
||||
}));
|
||||
affected_uuids.push(*dg_uuid);
|
||||
|
||||
pre_candidates.push(pre);
|
||||
candidates.push(d_group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write back the new changes.
|
||||
debug_assert!(pre_candidates.len() == candidates.len());
|
||||
// Write this stripe if populated.
|
||||
if !pre_candidates.is_empty() {
|
||||
qs.internal_batch_modify(pre_candidates, candidates)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to commit dyngroup set {:?}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(affected_uuids)
|
||||
}
|
||||
|
||||
// No post_delete handler is needed as refint takes care of this for us.
|
||||
|
||||
pub fn verify(_qs: &QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use kanidm_proto::v1::Filter as ProtoFilter;
|
||||
|
||||
const UUID_TEST_GROUP: Uuid = uuid::uuid!("7bfd9931-06c2-4608-8a46-78719bb746fe");
|
||||
|
||||
#[test]
|
||||
fn test_create_dyngroup_add_new_group() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_group];
|
||||
let create = vec![e_dyn];
|
||||
|
||||
run_create_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
// Need to validate it did things
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dyngroup_add_matching_entry() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn];
|
||||
let create = vec![e_group];
|
||||
|
||||
run_create_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
// Need to validate it did things
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dyngroup_add_non_matching_entry() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq(
|
||||
"name".to_string(),
|
||||
"no_possible_match_to_be_found".to_string()
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn];
|
||||
let create = vec![e_group];
|
||||
|
||||
run_create_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
// Need to validate it did things
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
assert!(d_group.get_ava_set("member").is_none());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dyngroup_add_matching_entry_and_group() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![];
|
||||
let create = vec![e_dyn, e_group];
|
||||
|
||||
run_create_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
// Need to validate it did things
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_dyngroup_existing_dyngroup_filter_into_scope() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq(
|
||||
"name".to_string(),
|
||||
"no_such_entry_exists".to_string()
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("test_dyngroup"))),
|
||||
ModifyList::new_list(vec![
|
||||
Modify::Purged("dyngroup_filter".into()),
|
||||
Modify::Present(
|
||||
AttrString::from("dyngroup_filter"),
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
]),
|
||||
None,
|
||||
|_| {},
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_dyngroup_existing_dyngroup_filter_outof_scope() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("test_dyngroup"))),
|
||||
ModifyList::new_list(vec![
|
||||
Modify::Purged("dyngroup_filter".into()),
|
||||
Modify::Present(
|
||||
AttrString::from("dyngroup_filter"),
|
||||
Value::JsonFilt(ProtoFilter::Eq(
|
||||
"name".to_string(),
|
||||
"no_such_entry_exists".to_string()
|
||||
))
|
||||
)
|
||||
]),
|
||||
None,
|
||||
|_| {},
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
assert!(d_group.get_ava_set("member").is_none());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_dyngroup_existing_dyngroup_member_add() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("test_dyngroup"))),
|
||||
ModifyList::new_list(vec![Modify::Present(
|
||||
AttrString::from("member"),
|
||||
Value::new_refer(*UUID_ADMIN)
|
||||
)]),
|
||||
None,
|
||||
|_| {},
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
// We assert to refer single here because we should have "removed" uuid_admin being added
|
||||
// at all.
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_dyngroup_existing_dyngroup_member_remove() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("test_dyngroup"))),
|
||||
ModifyList::new_list(vec![Modify::Purged(AttrString::from("member"),)]),
|
||||
None,
|
||||
|_| {},
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
// We assert to refer single here because we should have re-added the members
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_dyngroup_into_matching_entry() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("not_testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("not_testgroup"))),
|
||||
ModifyList::new_list(vec![
|
||||
Modify::Purged(AttrString::from("name"),),
|
||||
Modify::Present(AttrString::from("name"), Value::new_iname("testgroup"))
|
||||
]),
|
||||
None,
|
||||
|_| {},
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
let members = d_group
|
||||
.get_ava_set("member")
|
||||
.expect("No members on dyn group");
|
||||
|
||||
assert!(members.to_refer_single() == Some(UUID_TEST_GROUP));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_dyngroup_into_non_matching_entry() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("testgroup"))),
|
||||
ModifyList::new_list(vec![
|
||||
Modify::Purged(AttrString::from("name"),),
|
||||
Modify::Present(AttrString::from("name"), Value::new_iname("not_testgroup"))
|
||||
]),
|
||||
None,
|
||||
|_| {},
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
assert!(d_group.get_ava_set("member").is_none());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_dyngroup_matching_entry() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_delete_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("testgroup"))),
|
||||
None,
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq(
|
||||
"name",
|
||||
PartialValue::new_iname("test_dyngroup")
|
||||
)))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
assert!(d_group.get_ava_set("member").is_none());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_dyngroup_group() {
|
||||
let e_dyn = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("group")),
|
||||
("class", Value::new_class("dyngroup")),
|
||||
("name", Value::new_iname("test_dyngroup")),
|
||||
(
|
||||
"dyngroup_filter",
|
||||
Value::JsonFilt(ProtoFilter::Eq("name".to_string(), "testgroup".to_string()))
|
||||
)
|
||||
);
|
||||
|
||||
let e_group: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
("class", Value::new_class("group")),
|
||||
("name", Value::new_iname("testgroup")),
|
||||
("uuid", Value::new_uuid(UUID_TEST_GROUP))
|
||||
);
|
||||
|
||||
let preload = vec![e_dyn, e_group];
|
||||
|
||||
run_delete_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq("name", PartialValue::new_iname("test_dyngroup"))),
|
||||
None,
|
||||
|qs: &QueryServerWriteTransaction| {
|
||||
// Note we check memberof is empty here!
|
||||
let cands = qs
|
||||
.internal_search(filter!(f_eq("name", PartialValue::new_iname("testgroup"))))
|
||||
.expect("Internal search failure");
|
||||
|
||||
let d_group = cands.get(0).expect("Unable to access group.");
|
||||
assert!(d_group.get_ava_set("memberof").is_none());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
// As a result, we first need to run refint to clean up all dangling references, then memberof
|
||||
// fixes the graph of memberships
|
||||
|
||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntrySealed, EntryTuple};
|
||||
use crate::entry::{Entry, EntryCommitted, EntrySealed, EntryTuple};
|
||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||
use crate::plugins::Plugin;
|
||||
use crate::prelude::*;
|
||||
|
@ -20,7 +20,6 @@ use std::collections::BTreeSet;
|
|||
|
||||
use hashbrown::HashMap;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
lazy_static! {
|
||||
static ref CLASS_GROUP: PartialValue = PartialValue::new_class("group");
|
||||
|
@ -215,14 +214,18 @@ impl Plugin for MemberOf {
|
|||
"memberof"
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "memberof_post_create", skip(qs, cand, ce))]
|
||||
fn post_create(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
_ce: &CreateEvent,
|
||||
ce: &CreateEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
let dyngroup_change = super::dyngroup::DynGroup::post_create(qs, cand, ce)?;
|
||||
|
||||
let group_affect = cand
|
||||
.iter()
|
||||
.map(|e| e.get_uuid())
|
||||
.chain(dyngroup_change.into_iter())
|
||||
.chain(
|
||||
cand.iter()
|
||||
.filter_map(|e| {
|
||||
|
@ -240,16 +243,24 @@ impl Plugin for MemberOf {
|
|||
apply_memberof(qs, group_affect)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "memberof_post_modify",
|
||||
skip(qs, pre_cand, cand, me)
|
||||
)]
|
||||
fn post_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
_me: &ModifyEvent,
|
||||
me: &ModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
let dyngroup_change = super::dyngroup::DynGroup::post_modify(qs, pre_cand, cand, me)?;
|
||||
|
||||
// TODO: Limit this to when it's a class, member, mo, dmo change instead.
|
||||
let group_affect = cand
|
||||
.iter()
|
||||
.map(|post| post.get_uuid())
|
||||
.chain(dyngroup_change.into_iter())
|
||||
.chain(
|
||||
pre_cand
|
||||
.iter()
|
||||
|
@ -278,6 +289,7 @@ impl Plugin for MemberOf {
|
|||
apply_memberof(qs, group_affect)
|
||||
}
|
||||
|
||||
/*
|
||||
fn pre_delete(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
@ -298,11 +310,13 @@ impl Plugin for MemberOf {
|
|||
cand.iter_mut().for_each(|e| e.apply_modlist(&mo_purge));
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
#[instrument(level = "debug", name = "memberof_post_delete", skip(qs, cand, _de))]
|
||||
fn post_delete(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
_ce: &DeleteEvent,
|
||||
_de: &DeleteEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
// Similar condition to create - we only trigger updates on groups's members,
|
||||
// so that they can find they are no longer a mo of what was deleted.
|
||||
|
@ -322,6 +336,7 @@ impl Plugin for MemberOf {
|
|||
apply_memberof(qs, group_affect)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "memberof_verify", skip(qs))]
|
||||
fn verify(qs: &QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
||||
let mut r = Vec::new();
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ use tracing::trace_span;
|
|||
mod attrunique;
|
||||
mod base;
|
||||
mod domain;
|
||||
pub(crate) mod dyngroup;
|
||||
mod failure;
|
||||
mod gidnumber;
|
||||
mod memberof;
|
||||
|
@ -181,8 +182,8 @@ impl Plugins {
|
|||
) -> Result<(), OperationError> {
|
||||
spanned!("plugins::run_post_modify", {
|
||||
refint::ReferentialIntegrity::post_modify(qs, pre_cand, cand, me)
|
||||
.and_then(|_| memberof::MemberOf::post_modify(qs, pre_cand, cand, me))
|
||||
.and_then(|_| spn::Spn::post_modify(qs, pre_cand, cand, me))
|
||||
.and_then(|_| memberof::MemberOf::post_modify(qs, pre_cand, cand, me))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -216,6 +217,7 @@ impl Plugins {
|
|||
run_verify_plugin!(qs, results, base::Base);
|
||||
run_verify_plugin!(qs, results, attrunique::AttrUnique);
|
||||
run_verify_plugin!(qs, results, refint::ReferentialIntegrity);
|
||||
run_verify_plugin!(qs, results, dyngroup::DynGroup);
|
||||
run_verify_plugin!(qs, results, memberof::MemberOf);
|
||||
run_verify_plugin!(qs, results, spn::Spn);
|
||||
})
|
||||
|
|
|
@ -61,6 +61,11 @@ impl Plugin for Oauth2Secrets {
|
|||
"plugin_oauth2_secrets"
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "oauth2_pre_create_transform",
|
||||
skip(_qs, cand, _ce)
|
||||
)]
|
||||
fn pre_create_transform(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||
|
@ -69,6 +74,7 @@ impl Plugin for Oauth2Secrets {
|
|||
cand.iter_mut().try_for_each(|e| oauth2_transform!(e))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "oauth2_pre_modify", skip(_qs, cand, _me))]
|
||||
fn pre_modify(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
|
|
@ -14,6 +14,11 @@ impl Plugin for PasswordImport {
|
|||
"plugin_password_import"
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "password_import_pre_create_transform",
|
||||
skip(_qs, cand, _ce)
|
||||
)]
|
||||
fn pre_create_transform(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||
|
@ -57,6 +62,11 @@ impl Plugin for PasswordImport {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "password_import_pre_modify",
|
||||
skip(_qs, cand, _me)
|
||||
)]
|
||||
fn pre_modify(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
|
|
@ -36,12 +36,14 @@ lazy_static! {
|
|||
static ref PVCLASS_DOMAIN_INFO: PartialValue = PartialValue::new_class("domain_info");
|
||||
static ref PVCLASS_SYSTEM_INFO: PartialValue = PartialValue::new_class("system_info");
|
||||
static ref PVCLASS_SYSTEM_CONFIG: PartialValue = PartialValue::new_class("system_config");
|
||||
static ref PVCLASS_DYNGROUP: PartialValue = PartialValue::new_class("dyngroup");
|
||||
static ref VCLASS_SYSTEM: Value = Value::new_class("system");
|
||||
static ref VCLASS_TOMBSTONE: Value = Value::new_class("tombstone");
|
||||
static ref VCLASS_RECYCLED: Value = Value::new_class("recycled");
|
||||
static ref VCLASS_DOMAIN_INFO: Value = Value::new_class("domain_info");
|
||||
static ref VCLASS_SYSTEM_INFO: Value = Value::new_class("system_info");
|
||||
static ref VCLASS_SYSTEM_CONFIG: Value = Value::new_class("system_config");
|
||||
static ref VCLASS_DYNGROUP: Value = Value::new_class("dyngroup");
|
||||
}
|
||||
|
||||
impl Plugin for Protected {
|
||||
|
@ -49,6 +51,7 @@ impl Plugin for Protected {
|
|||
"plugin_protected"
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_create", skip(_qs, cand, ce))]
|
||||
fn pre_create(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
// List of what we will commit that is valid?
|
||||
|
@ -67,6 +70,7 @@ impl Plugin for Protected {
|
|||
|| cand.attribute_equality("class", &PVCLASS_SYSTEM_CONFIG)
|
||||
|| cand.attribute_equality("class", &PVCLASS_TOMBSTONE)
|
||||
|| cand.attribute_equality("class", &PVCLASS_RECYCLED)
|
||||
|| cand.attribute_equality("class", &PVCLASS_DYNGROUP)
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
|
@ -75,6 +79,7 @@ impl Plugin for Protected {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_modify", skip(_qs, cand, me))]
|
||||
fn pre_modify(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
// Should these be EntrySealed?
|
||||
|
@ -94,6 +99,7 @@ impl Plugin for Protected {
|
|||
|| v == &(*VCLASS_DOMAIN_INFO)
|
||||
|| v == &(*VCLASS_SYSTEM_INFO)
|
||||
|| v == &(*VCLASS_SYSTEM_CONFIG)
|
||||
|| v == &(*VCLASS_DYNGROUP)
|
||||
|| v == &(*VCLASS_TOMBSTONE)
|
||||
|| v == &(*VCLASS_RECYCLED))
|
||||
{
|
||||
|
@ -110,6 +116,7 @@ impl Plugin for Protected {
|
|||
cand.iter().try_fold((), |(), cand| {
|
||||
if cand.attribute_equality("class", &PVCLASS_TOMBSTONE)
|
||||
|| cand.attribute_equality("class", &PVCLASS_RECYCLED)
|
||||
|| cand.attribute_equality("class", &PVCLASS_DYNGROUP)
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
|
@ -143,6 +150,7 @@ impl Plugin for Protected {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_delete", skip(_qs, cand, de))]
|
||||
fn pre_delete(
|
||||
_qs: &QueryServerWriteTransaction,
|
||||
// Should these be EntrySealed
|
||||
|
@ -161,6 +169,7 @@ impl Plugin for Protected {
|
|||
|| cand.attribute_equality("class", &PVCLASS_SYSTEM_CONFIG)
|
||||
|| cand.attribute_equality("class", &PVCLASS_TOMBSTONE)
|
||||
|| cand.attribute_equality("class", &PVCLASS_RECYCLED)
|
||||
|| cand.attribute_equality("class", &PVCLASS_DYNGROUP)
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
|
|
|
@ -82,6 +82,7 @@ impl Plugin for ReferentialIntegrity {
|
|||
// so we can assert stronger trust in it's correct operation and interaction
|
||||
// in complex scenarioes - It actually simplifies the check from "could
|
||||
// be in cand AND db" to simply "is it in the DB?".
|
||||
#[instrument(level = "debug", name = "refint_post_create", skip(qs, cand, _ce))]
|
||||
fn post_create(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
|
@ -118,6 +119,11 @@ impl Plugin for ReferentialIntegrity {
|
|||
Self::check_uuids_exist(qs, i)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "refint_post_modify",
|
||||
skip(qs, _pre_cand, _cand, me)
|
||||
)]
|
||||
fn post_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
|
||||
|
@ -157,6 +163,7 @@ impl Plugin for ReferentialIntegrity {
|
|||
Self::check_uuids_exist(qs, i)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "refint_post_delete", skip(qs, cand, _ce))]
|
||||
fn post_delete(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||
|
@ -209,6 +216,7 @@ impl Plugin for ReferentialIntegrity {
|
|||
qs.internal_batch_modify(pre_candidates, candidates)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "verify", skip(qs))]
|
||||
fn verify(qs: &QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
||||
// Get all entries as cand
|
||||
// build a cand-uuid set
|
||||
|
|
|
@ -26,6 +26,11 @@ impl Plugin for Spn {
|
|||
}
|
||||
|
||||
// hook on pre-create and modify to generate / validate.
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "spn_pre_create_transform",
|
||||
skip(qs, cand, _ce)
|
||||
)]
|
||||
fn pre_create_transform(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||
|
@ -60,6 +65,7 @@ impl Plugin for Spn {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "spn_pre_modify", skip(qs, cand, _me))]
|
||||
fn pre_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
|
@ -90,6 +96,11 @@ impl Plugin for Spn {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
name = "spn_post_modify",
|
||||
skip(qs, pre_cand, cand, _ce)
|
||||
)]
|
||||
fn post_modify(
|
||||
qs: &QueryServerWriteTransaction,
|
||||
// List of what we modified that was valid?
|
||||
|
@ -133,6 +144,7 @@ impl Plugin for Spn {
|
|||
)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "spn_verify", skip(qs))]
|
||||
fn verify(qs: &QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
||||
// Verify that all items with spn's have valid spns.
|
||||
// We need to consider the case that an item has a different origin domain too,
|
||||
|
|
|
@ -27,6 +27,7 @@ use crate::event::{
|
|||
use crate::filter::{Filter, FilterInvalid, FilterValid, FilterValidResolved};
|
||||
use crate::identity::IdentityId;
|
||||
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
|
||||
use crate::plugins::dyngroup::{DynGroup, DynGroupCache};
|
||||
use crate::plugins::Plugins;
|
||||
use crate::repl::cid::Cid;
|
||||
use crate::schema::{
|
||||
|
@ -56,6 +57,13 @@ lazy_static! {
|
|||
static ref PVACP_ENABLE_FALSE: PartialValue = PartialValue::new_bool(false);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, PartialEq, Eq)]
|
||||
enum ServerPhase {
|
||||
Bootstrap,
|
||||
SchemaReady,
|
||||
Running,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DomainInfo {
|
||||
d_uuid: Uuid,
|
||||
|
@ -65,6 +73,7 @@ struct DomainInfo {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct QueryServer {
|
||||
phase: Arc<CowCell<ServerPhase>>,
|
||||
s_uuid: Uuid,
|
||||
d_info: Arc<CowCell<DomainInfo>>,
|
||||
be: Backend,
|
||||
|
@ -74,6 +83,7 @@ pub struct QueryServer {
|
|||
write_ticket: Arc<Semaphore>,
|
||||
resolve_filter_cache:
|
||||
Arc<ARCache<(IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>>,
|
||||
dyngroup_cache: Arc<CowCell<DynGroupCache>>,
|
||||
}
|
||||
|
||||
pub struct QueryServerReadTransaction<'a> {
|
||||
|
@ -94,6 +104,7 @@ unsafe impl<'a> Send for QueryServerReadTransaction<'a> {}
|
|||
|
||||
pub struct QueryServerWriteTransaction<'a> {
|
||||
committed: bool,
|
||||
phase: CowCellWriteTxn<'a, ServerPhase>,
|
||||
d_info: CowCellWriteTxn<'a, DomainInfo>,
|
||||
cid: Cid,
|
||||
be_txn: BackendWriteTransaction<'a>,
|
||||
|
@ -112,6 +123,7 @@ pub struct QueryServerWriteTransaction<'a> {
|
|||
_write_ticket: SemaphorePermit<'a>,
|
||||
resolve_filter_cache:
|
||||
Cell<ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>>,
|
||||
dyngroup_cache: Cell<CowCellWriteTxn<'a, DynGroupCache>>,
|
||||
}
|
||||
|
||||
pub(crate) struct ModifyPartial<'a> {
|
||||
|
@ -982,10 +994,15 @@ impl QueryServer {
|
|||
d_display: domain_name,
|
||||
}));
|
||||
|
||||
let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
|
||||
|
||||
let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
|
||||
|
||||
// log_event!(log, "Starting query worker ...");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
QueryServer {
|
||||
phase,
|
||||
s_uuid,
|
||||
d_info,
|
||||
be,
|
||||
|
@ -1000,6 +1017,7 @@ impl QueryServer {
|
|||
.build()
|
||||
.expect("Failed to build resolve_filter_cache"),
|
||||
),
|
||||
dyngroup_cache,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1059,6 +1077,7 @@ impl QueryServer {
|
|||
let schema_write = self.schema.write();
|
||||
let be_txn = self.be.write();
|
||||
let d_info = self.d_info.write();
|
||||
let phase = self.phase.write();
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let ts_max = be_txn.get_db_ts_max(ts).expect("Unable to get db_ts_max");
|
||||
|
@ -1072,6 +1091,7 @@ impl QueryServer {
|
|||
// The commited flag is however used for abort-specific code in drop
|
||||
// which today I don't think we have ... yet.
|
||||
committed: false,
|
||||
phase,
|
||||
d_info,
|
||||
cid,
|
||||
be_txn,
|
||||
|
@ -1085,6 +1105,7 @@ impl QueryServer {
|
|||
_db_ticket: db_ticket,
|
||||
_write_ticket: write_ticket,
|
||||
resolve_filter_cache: Cell::new(self.resolve_filter_cache.read()),
|
||||
dyngroup_cache: Cell::new(self.dyngroup_cache.write()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1132,7 +1153,11 @@ impl QueryServer {
|
|||
|
||||
// Force the schema to reload - this is so that any changes to index slope
|
||||
// analysis are now reflected correctly.
|
||||
let slope_reload = task::block_on(self.write_async(ts));
|
||||
//
|
||||
// A side effect of these reloads is that other plugins or elements that reload
|
||||
// on schema change are now setup.
|
||||
let mut slope_reload = task::block_on(self.write_async(ts));
|
||||
slope_reload.set_phase(ServerPhase::SchemaReady);
|
||||
slope_reload.force_schema_reload();
|
||||
slope_reload.commit()?;
|
||||
|
||||
|
@ -1178,10 +1203,11 @@ impl QueryServer {
|
|||
migrate_txn.commit()?;
|
||||
// Migrations complete. Init idm will now set the version as needed.
|
||||
|
||||
let ts_write_3 = task::block_on(self.write_async(ts));
|
||||
ts_write_3
|
||||
.initialise_idm()
|
||||
.and_then(|_| ts_write_3.commit())?;
|
||||
let mut ts_write_3 = task::block_on(self.write_async(ts));
|
||||
ts_write_3.initialise_idm().and_then(|_| {
|
||||
ts_write_3.set_phase(ServerPhase::Running);
|
||||
ts_write_3.commit()
|
||||
})?;
|
||||
// TODO: work out if we've actually done any migrations before printing this
|
||||
admin_debug!("Database version check and migrations success! ☀️ ");
|
||||
Ok(())
|
||||
|
@ -2165,6 +2191,13 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_dyngroup_cache(&self) -> &mut DynGroupCache {
|
||||
unsafe {
|
||||
let mptr = self.dyngroup_cache.as_ptr();
|
||||
(*mptr).get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate 2 to 3 changes the name, domain_name types from iutf8 to iname.
|
||||
pub fn migrate_2_to_3(&self) -> Result<(), OperationError> {
|
||||
spanned!("server::migrate_2_to_3", {
|
||||
|
@ -2599,9 +2632,11 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
JSON_SCHEMA_ATTR_PASSKEYS,
|
||||
JSON_SCHEMA_ATTR_DEVICEKEYS,
|
||||
JSON_SCHEMA_ATTR_DYNGROUP_FILTER,
|
||||
JSON_SCHEMA_CLASS_PERSON,
|
||||
JSON_SCHEMA_CLASS_ORGPERSON,
|
||||
JSON_SCHEMA_CLASS_GROUP,
|
||||
JSON_SCHEMA_CLASS_DYNGROUP,
|
||||
JSON_SCHEMA_CLASS_ACCOUNT,
|
||||
JSON_SCHEMA_CLASS_SERVICE_ACCOUNT,
|
||||
JSON_SCHEMA_CLASS_DOMAIN_INFO,
|
||||
|
@ -2669,6 +2704,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
|
||||
// Create any system default access profile entries.
|
||||
let idm_entries = [
|
||||
// Builtin dyn groups,
|
||||
JSON_IDM_ALL_PERSONS,
|
||||
// Builtin groups
|
||||
JSON_IDM_PEOPLE_MANAGE_PRIV_V1,
|
||||
JSON_IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1,
|
||||
|
@ -2763,66 +2800,72 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "info", name = "reload_schema", skip(self))]
|
||||
fn reload_schema(&mut self) -> Result<(), OperationError> {
|
||||
spanned!("server::reload_schema", {
|
||||
// supply entries to the writable schema to reload from.
|
||||
// find all attributes.
|
||||
let filt = filter!(f_eq("class", PVCLASS_ATTRIBUTETYPE.clone()));
|
||||
let res = self.internal_search(filt).map_err(|e| {
|
||||
admin_error!("reload schema internal search failed {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// load them.
|
||||
let attributetypes: Result<Vec<_>, _> =
|
||||
res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
|
||||
let attributetypes = attributetypes.map_err(|e| {
|
||||
admin_error!("reload schema attributetypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// supply entries to the writable schema to reload from.
|
||||
// find all attributes.
|
||||
let filt = filter!(f_eq("class", PVCLASS_ATTRIBUTETYPE.clone()));
|
||||
let res = self.internal_search(filt).map_err(|e| {
|
||||
admin_error!("reload schema internal search failed {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// load them.
|
||||
let attributetypes: Result<Vec<_>, _> =
|
||||
res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
|
||||
let attributetypes = attributetypes.map_err(|e| {
|
||||
admin_error!("reload schema attributetypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
self.schema.update_attributes(attributetypes).map_err(|e| {
|
||||
admin_error!("reload schema update attributetypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
self.schema.update_attributes(attributetypes).map_err(|e| {
|
||||
admin_error!("reload schema update attributetypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
// find all classes
|
||||
let filt = filter!(f_eq("class", PVCLASS_CLASSTYPE.clone()));
|
||||
let res = self.internal_search(filt).map_err(|e| {
|
||||
admin_error!("reload schema internal search failed {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// load them.
|
||||
let classtypes: Result<Vec<_>, _> =
|
||||
res.iter().map(|e| SchemaClass::try_from(e)).collect();
|
||||
let classtypes = classtypes.map_err(|e| {
|
||||
admin_error!("reload schema classtypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// find all classes
|
||||
let filt = filter!(f_eq("class", PVCLASS_CLASSTYPE.clone()));
|
||||
let res = self.internal_search(filt).map_err(|e| {
|
||||
admin_error!("reload schema internal search failed {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// load them.
|
||||
let classtypes: Result<Vec<_>, _> = res.iter().map(|e| SchemaClass::try_from(e)).collect();
|
||||
let classtypes = classtypes.map_err(|e| {
|
||||
admin_error!("reload schema classtypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
self.schema.update_classes(classtypes).map_err(|e| {
|
||||
admin_error!("reload schema update classtypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
self.schema.update_classes(classtypes).map_err(|e| {
|
||||
admin_error!("reload schema update classtypes {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
// validate.
|
||||
let valid_r = self.schema.validate();
|
||||
// validate.
|
||||
let valid_r = self.schema.validate();
|
||||
|
||||
// Translate the result.
|
||||
if valid_r.is_empty() {
|
||||
// Now use this to reload the backend idxmeta
|
||||
trace!("Reloading idxmeta ...");
|
||||
self.be_txn
|
||||
.update_idxmeta(self.schema.reload_idxmeta())
|
||||
.map_err(|e| {
|
||||
admin_error!("reload schema update idxmeta {:?}", e);
|
||||
e
|
||||
})
|
||||
} else {
|
||||
// Log the failures?
|
||||
admin_error!("Schema reload failed -> {:?}", valid_r);
|
||||
Err(OperationError::ConsistencyError(valid_r))
|
||||
}
|
||||
})
|
||||
// Translate the result.
|
||||
if valid_r.is_empty() {
|
||||
// Now use this to reload the backend idxmeta
|
||||
trace!("Reloading idxmeta ...");
|
||||
self.be_txn
|
||||
.update_idxmeta(self.schema.reload_idxmeta())
|
||||
.map_err(|e| {
|
||||
admin_error!("reload schema update idxmeta {:?}", e);
|
||||
e
|
||||
})
|
||||
} else {
|
||||
// Log the failures?
|
||||
admin_error!("Schema reload failed -> {:?}", valid_r);
|
||||
Err(OperationError::ConsistencyError(valid_r))
|
||||
}?;
|
||||
|
||||
// Trigger reloads on services that require post-schema reloads.
|
||||
// Mainly this is plugins.
|
||||
if *self.phase >= ServerPhase::SchemaReady {
|
||||
DynGroup::reload(self)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
|
||||
|
@ -3053,6 +3096,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
self.changed_domain.get()
|
||||
}
|
||||
|
||||
fn set_phase(&mut self, phase: ServerPhase) {
|
||||
*self.phase = phase
|
||||
}
|
||||
|
||||
pub fn commit(mut self) -> Result<(), OperationError> {
|
||||
// This could be faster if we cache the set of classes changed
|
||||
// in an operation so we can check if we need to do the reload or not
|
||||
|
@ -3081,11 +3128,13 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
// Now destructure the transaction ready to reset it.
|
||||
let QueryServerWriteTransaction {
|
||||
committed,
|
||||
phase,
|
||||
be_txn,
|
||||
schema,
|
||||
d_info,
|
||||
accesscontrols,
|
||||
cid,
|
||||
dyngroup_cache,
|
||||
..
|
||||
} = self;
|
||||
debug_assert!(!committed);
|
||||
|
@ -3102,6 +3151,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
schema
|
||||
.commit()
|
||||
.map(|_| d_info.commit())
|
||||
.map(|_| phase.commit())
|
||||
.map(|_| dyngroup_cache.into_inner().commit())
|
||||
.and_then(|_| accesscontrols.commit())
|
||||
.and_then(|_| be_txn.commit())
|
||||
} else {
|
||||
|
@ -3135,7 +3186,7 @@ mod tests {
|
|||
let se1 = unsafe { SearchEvent::new_impersonate_entry(admin.clone(), filt.clone()) };
|
||||
let se2 = unsafe { SearchEvent::new_impersonate_entry(admin, filt) };
|
||||
|
||||
let e = entry_init!(
|
||||
let mut e = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("person")),
|
||||
("name", Value::new_iname("testperson")),
|
||||
|
@ -3159,6 +3210,11 @@ mod tests {
|
|||
debug!("--> {:?}", r2);
|
||||
assert!(r2.len() == 1);
|
||||
|
||||
// We apply some member-of in the server now, so we add these before we seal.
|
||||
e.add_ava("class", Value::new_class("memberof"));
|
||||
e.add_ava("memberof", Value::new_refer(UUID_IDM_ALL_PERSONS));
|
||||
e.add_ava("directmemberof", Value::new_refer(UUID_IDM_ALL_PERSONS));
|
||||
|
||||
let expected = unsafe { vec![Arc::new(e.into_sealed_committed())] };
|
||||
|
||||
assert_eq!(r2, expected);
|
||||
|
|
Loading…
Reference in a new issue