use crate::plugins::Plugin;
use hashbrown::HashSet;
use std::collections::BTreeSet;
use uuid::Uuid;
use crate::audit::AuditScope;
use crate::constants::{UUID_ADMIN, UUID_ANONYMOUS, UUID_DOES_NOT_EXIST};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
use crate::event::{CreateEvent, ModifyEvent};
use crate::modify::Modify;
use crate::server::{
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
};
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::{ConsistencyError, OperationError, PluginError};
lazy_static! {
static ref CLASS_OBJECT: Value = Value::new_class("object");
}
pub struct Base {}
impl Plugin for Base {
fn id() -> &'static str {
"plugin_base"
}
#[allow(clippy::cognitive_complexity)]
fn pre_create_transform(
au: &mut AuditScope,
qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
ce: &CreateEvent,
) -> Result<(), OperationError> {
for entry in cand.iter_mut() {
ltrace!(au, "Base check on entry: {:?}", entry);
entry.add_ava("class", CLASS_OBJECT.clone());
ltrace!(au, "Object should now be in entry: {:?}", entry);
match entry.get_ava_set("uuid").map(|s| s.len()) {
None => {
let ava_uuid = btreeset![Value::new_uuid(Uuid::new_v4())];
ltrace!(au, "Setting temporary UUID {:?} to entry", ava_uuid);
entry.set_ava("uuid", ava_uuid);
}
Some(1) => {
}
Some(x) => {
ladmin_error!(
au,
"Entry defines uuid attr, but has multiple ({}) values.",
x
);
return Err(OperationError::Plugin(PluginError::Base(
"Uuid has multiple values".to_string(),
)));
}
};
}
let mut cand_uuid: BTreeSet<&Uuid> = BTreeSet::new();
for entry in cand.iter() {
let uuid_ref: &Uuid = entry
.get_ava_single("uuid")
.ok_or(OperationError::InvalidEntryState)?
.to_uuid()
.ok_or_else(|| OperationError::InvalidAttribute("uuid".to_string()))?;
ltrace!(au, "Entry valid UUID: {:?}", entry);
if !cand_uuid.insert(uuid_ref) {
ltrace!(au, "uuid duplicate found in create set! {:?}", uuid_ref);
return Err(OperationError::Plugin(PluginError::Base(
"Uuid duplicate detected in request".to_string(),
)));
}
}
let uuid_admin = *UUID_ADMIN;
let uuid_anonymous = *UUID_ANONYMOUS;
let uuid_does_not_exist = *UUID_DOES_NOT_EXIST;
if !ce.event.is_internal() {
let overlap: usize = cand_uuid.range(uuid_admin..uuid_anonymous).count();
if overlap != 0 {
ladmin_error!(
au,
"uuid from protected system UUID range found in create set! {:?}",
overlap
);
return Err(OperationError::Plugin(PluginError::Base(
"Uuid must not be in protected range".to_string(),
)));
}
}
if cand_uuid.contains(&uuid_does_not_exist) {
ladmin_error!(
au,
"uuid \"does not exist\" found in create set! {:?}",
uuid_does_not_exist
);
return Err(OperationError::Plugin(PluginError::Base(
"UUID_DOES_NOT_EXIST may not exist!".to_string(),
)));
}
let filt_in = filter_all!(FC::Or(
cand_uuid
.iter()
.map(|u| FC::Eq("uuid", PartialValue::new_uuid(**u)))
.collect(),
));
let r = qs.internal_exists(au, filt_in);
match r {
Ok(b) => {
if b {
ladmin_error!(au, "A UUID already exists, rejecting.");
return Err(OperationError::Plugin(PluginError::Base(
"Uuid duplicate found in database".to_string(),
)));
}
}
Err(e) => {
ladmin_error!(au, "Error occured checking UUID existance. {:?}", e);
return Err(e);
}
}
Ok(())
}
fn pre_modify(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
me: &ModifyEvent,
) -> Result<(), OperationError> {
for modify in me.modlist.into_iter() {
let attr = match &modify {
Modify::Present(a, _) => a,
Modify::Removed(a, _) => a,
Modify::Purged(a) => a,
};
if attr == "uuid" {
lrequest_error!(au, "Modifications to UUID's are NOT ALLOWED");
return Err(OperationError::SystemProtectedAttribute);
}
}
Ok(())
}
fn verify(
au: &mut AuditScope,
qs: &QueryServerReadTransaction,
) -> Vec<Result<(), ConsistencyError>> {
let entries = match qs.internal_search(au, filter!(f_pres("class"))) {
Ok(v) => v,
Err(e) => {
ladmin_error!(au, "Internal Search Failure: {:?}", e);
return vec![Err(ConsistencyError::QueryServerSearchFailure)];
}
};
let mut uuid_seen: HashSet<Uuid> = HashSet::with_capacity(entries.len());
entries
.iter()
.map(|e| {
let uuid = e.get_uuid();
if uuid_seen.insert(*uuid) {
Ok(())
} else {
Err(ConsistencyError::UuidNotUnique(uuid.to_string()))
}
})
.filter(|v| v.is_err())
.collect()
}
}
#[cfg(test)]
mod tests {
use crate::constants::JSON_ADMIN_V1;
use crate::entry::{Entry, EntryInit, EntryNew};
use crate::modify::{Modify, ModifyList};
use crate::server::QueryServerTransaction;
use crate::server::QueryServerWriteTransaction;
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::{OperationError, PluginError};
const JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
"attrs": {
"class": [
"object",
"access_control_profile",
"access_control_modify",
"access_control_create",
"access_control_delete",
"access_control_search"
],
"name": ["idm_admins_acp_allow_all_test"],
"uuid": ["bb18f746-a409-497d-928c-5455d4aef4f7"],
"description": ["Builtin IDM Administrators Access Controls."],
"acp_enable": ["true"],
"acp_receiver": [
"{\"eq\":[\"uuid\",\"00000000-0000-0000-0000-000000000000\"]}"
],
"acp_targetscope": [
"{\"pres\":\"class\"}"
],
"acp_search_attr": ["name", "class", "uuid"],
"acp_modify_class": ["system"],
"acp_modify_removedattr": ["class", "displayname", "may", "must"],
"acp_modify_presentattr": ["class", "displayname", "may", "must"],
"acp_create_class": ["object", "person", "system"],
"acp_create_attr": ["name", "class", "description", "displayname", "uuid"]
}
}"#;
#[test]
fn test_pre_create_no_uuid() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e];
run_create_test!(
Ok(()),
preload,
create,
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let cands = qs
.internal_search(
au,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
)
.expect("Internal search failure");
let ue = cands.first().expect("No cand");
assert!(ue.attribute_pres("uuid"));
}
);
}
#[test]
fn test_pre_create_uuid_invalid() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["xxxxxx"]
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::InvalidAttribute("uuid".to_string())),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_pre_create_uuid_empty() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": []
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::Plugin(PluginError::Base(
"Uuid format invalid".to_string()
))),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_pre_create_uuid_valid() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(
Ok(()),
preload,
create,
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let cands = qs
.internal_search(
au,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
)
.expect("Internal search failure");
let ue = cands.first().expect("No cand");
assert!(ue.attribute_equality(
"uuid",
&PartialValue::new_uuids("79724141-3603-4060-b6bb-35c72772611d").unwrap()
));
}
);
}
#[test]
fn test_pre_create_uuid_valid_multi() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["79724141-3603-4060-b6bb-35c72772611d", "79724141-3603-4060-b6bb-35c72772611e"]
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::Plugin(PluginError::Base(
"Uuid has multiple values".to_string()
))),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_pre_create_uuid_exist() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
);
let create = vec![e.clone()];
let preload = vec![e];
run_create_test!(
Err(OperationError::Plugin(PluginError::Base(
"Uuid duplicate found in database".to_string()
))),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_pre_create_double_uuid() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson_a"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
);
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person"],
"name": ["testperson_a"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["79724141-3603-4060-b6bb-35c72772611d"]
}
}"#,
);
let create = vec![ea, eb];
run_create_test!(
Err(OperationError::Plugin(PluginError::Base(
"Uuid duplicate detected in request".to_string()
))),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_modify_uuid_present() {
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["group"],
"name": ["testgroup_a"],
"description": ["testgroup"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
);
let preload = vec![ea];
run_modify_test!(
Err(OperationError::SystemProtectedAttribute),
preload,
filter!(f_eq("name", PartialValue::new_iname("testgroup_a"))),
ModifyList::new_list(vec![Modify::Present(
"uuid".to_string(),
Value::from("f15a7219-1d15-44e3-a7b4-bec899c07788")
)]),
None,
|_, _| {}
);
}
#[test]
fn test_modify_uuid_removed() {
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["group"],
"name": ["testgroup_a"],
"description": ["testgroup"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
);
let preload = vec![ea];
run_modify_test!(
Err(OperationError::SystemProtectedAttribute),
preload,
filter!(f_eq("name", PartialValue::new_iname("testgroup_a"))),
ModifyList::new_list(vec![Modify::Removed(
"uuid".to_string(),
PartialValue::new_uuids("f15a7219-1d15-44e3-a7b4-bec899c07788").unwrap()
)]),
None,
|_, _| {}
);
}
#[test]
fn test_modify_uuid_purged() {
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["group"],
"name": ["testgroup_a"],
"description": ["testgroup"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
);
let preload = vec![ea];
run_modify_test!(
Err(OperationError::SystemProtectedAttribute),
preload,
filter!(f_eq("name", PartialValue::new_iname("testgroup_a"))),
ModifyList::new_list(vec![Modify::Purged("uuid".to_string())]),
None,
|_, _| {}
);
}
#[test]
fn test_protected_uuid_range() {
let acp: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
let preload = vec![acp];
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person", "system"],
"name": ["testperson"],
"uuid": ["00000000-0000-0000-0000-f0f0f0f0f0f0"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::Plugin(PluginError::Base(
"Uuid must not be in protected range".to_string()
))),
preload,
create,
Some(JSON_ADMIN_V1),
|_, _| {}
);
}
#[test]
fn test_protected_uuid_does_not_exist() {
let preload = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person", "system"],
"name": ["testperson"],
"uuid": ["00000000-0000-0000-0000-fffffffffffe"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(
Err(OperationError::Plugin(PluginError::Base(
"UUID_DOES_NOT_EXIST may not exist!".to_string()
))),
preload,
create,
None,
|_, _| {}
);
}
}