mirror of
https://github.com/kanidm/kanidm.git
synced 2025-04-30 14:15:05 +02:00
691 lines
25 KiB
Rust
691 lines
25 KiB
Rust
// System protected objects. Items matching specific requirements
|
|
// may only have certain modifications performed.
|
|
|
|
use hashbrown::HashSet;
|
|
use std::sync::Arc;
|
|
|
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
|
use crate::modify::Modify;
|
|
use crate::plugins::Plugin;
|
|
use crate::prelude::*;
|
|
|
|
pub struct Protected {}
|
|
|
|
// Here is the declaration of all the attrs that can be altered by
|
|
// a call on a system object. We trust they are allowed because
|
|
// schema will have checked this, and we don't allow class changes!
|
|
|
|
lazy_static! {
|
|
static ref ALLOWED_ATTRS: HashSet<Attribute> = {
|
|
let attrs = vec![
|
|
// Allow modification of some schema class types to allow local extension
|
|
// of schema types.
|
|
Attribute::Must,
|
|
Attribute::May,
|
|
// modification of some domain info types for local configuratiomn.
|
|
Attribute::DomainSsid,
|
|
Attribute::DomainLdapBasedn,
|
|
Attribute::LdapMaxQueryableAttrs,
|
|
Attribute::LdapAllowUnixPwBind,
|
|
Attribute::FernetPrivateKeyStr,
|
|
Attribute::Es256PrivateKeyDer,
|
|
Attribute::KeyActionRevoke,
|
|
Attribute::KeyActionRotate,
|
|
Attribute::IdVerificationEcKey,
|
|
Attribute::BadlistPassword,
|
|
Attribute::DeniedName,
|
|
Attribute::DomainDisplayName,
|
|
Attribute::Image,
|
|
// modification of account policy values for dyngroup.
|
|
Attribute::AuthSessionExpiry,
|
|
Attribute::AuthPasswordMinimumLength,
|
|
Attribute::CredentialTypeMinimum,
|
|
Attribute::PrivilegeExpiry,
|
|
Attribute::WebauthnAttestationCaList,
|
|
Attribute::LimitSearchMaxResults,
|
|
Attribute::LimitSearchMaxFilterTest,
|
|
Attribute::AllowPrimaryCredFallback,
|
|
];
|
|
|
|
let mut m = HashSet::with_capacity(attrs.len());
|
|
m.extend(attrs);
|
|
|
|
m
|
|
};
|
|
|
|
static ref PROTECTED_ENTRYCLASSES: Vec<EntryClass> =
|
|
vec![
|
|
EntryClass::System,
|
|
EntryClass::DomainInfo,
|
|
EntryClass::SystemInfo,
|
|
EntryClass::SystemConfig,
|
|
EntryClass::DynGroup,
|
|
EntryClass::SyncObject,
|
|
EntryClass::Tombstone,
|
|
EntryClass::Recycled,
|
|
];
|
|
}
|
|
|
|
impl Plugin for Protected {
|
|
fn id() -> &'static str {
|
|
"plugin_protected"
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "protected_pre_create", skip_all)]
|
|
fn pre_create(
|
|
_qs: &mut QueryServerWriteTransaction,
|
|
// List of what we will commit that is valid?
|
|
cand: &[Entry<EntrySealed, EntryNew>],
|
|
ce: &CreateEvent,
|
|
) -> Result<(), OperationError> {
|
|
if ce.ident.is_internal() {
|
|
trace!("Internal operation, not enforcing system object protection");
|
|
return Ok(());
|
|
}
|
|
|
|
cand.iter().try_fold((), |(), cand| {
|
|
if PROTECTED_ENTRYCLASSES
|
|
.iter()
|
|
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
|
{
|
|
trace!("Rejecting operation during pre_create check");
|
|
Err(OperationError::SystemProtectedObject)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "protected_pre_modify", skip_all)]
|
|
fn pre_modify(
|
|
_qs: &mut QueryServerWriteTransaction,
|
|
_pre_cand: &[Arc<EntrySealedCommitted>],
|
|
cand: &mut Vec<EntryInvalidCommitted>,
|
|
me: &ModifyEvent,
|
|
) -> Result<(), OperationError> {
|
|
if me.ident.is_internal() {
|
|
trace!("Internal operation, not enforcing system object protection");
|
|
return Ok(());
|
|
}
|
|
// Prevent adding class: system, domain_info, tombstone, or recycled.
|
|
me.modlist.iter().try_fold((), |(), m| match m {
|
|
Modify::Present(a, v) => {
|
|
if a == Attribute::Class.as_ref()
|
|
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
|
{
|
|
trace!("Rejecting operation during pre_modify check");
|
|
Err(OperationError::SystemProtectedObject)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
_ => Ok(()),
|
|
})?;
|
|
|
|
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
|
// have some allowed attrs.
|
|
cand.iter().try_fold((), |(), cand| {
|
|
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
|
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
|
{
|
|
Err(OperationError::SystemProtectedObject)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})?;
|
|
|
|
// if class: system, check the mods are "allowed"
|
|
let system_pres = cand.iter().any(|c| {
|
|
// We don't need to check for domain info here because domain_info has a class
|
|
// system also. We just need to block it from being created.
|
|
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
|
});
|
|
|
|
trace!("class: system -> {}", system_pres);
|
|
// No system types being altered, return.
|
|
if !system_pres {
|
|
return Ok(());
|
|
}
|
|
|
|
// Something altered is system, check if it's allowed.
|
|
me.modlist.into_iter().try_fold((), |(), m| {
|
|
// Already hit an error, move on.
|
|
let a = match m {
|
|
Modify::Present(a, _)
|
|
| Modify::Removed(a, _)
|
|
| Modify::Set(a, _)
|
|
| Modify::Purged(a) => Some(a),
|
|
Modify::Assert(_, _) => None,
|
|
};
|
|
if let Some(attr) = a {
|
|
match ALLOWED_ATTRS.contains(attr) {
|
|
true => Ok(()),
|
|
false => {
|
|
trace!("If you're getting this, you need to modify the ALLOWED_ATTRS list");
|
|
Err(OperationError::SystemProtectedObject)
|
|
}
|
|
}
|
|
} else {
|
|
// Was not a mod needing checking
|
|
Ok(())
|
|
}
|
|
})
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "protected_pre_batch_modify", skip_all)]
|
|
fn pre_batch_modify(
|
|
_qs: &mut QueryServerWriteTransaction,
|
|
_pre_cand: &[Arc<EntrySealedCommitted>],
|
|
cand: &mut Vec<EntryInvalidCommitted>,
|
|
me: &BatchModifyEvent,
|
|
) -> Result<(), OperationError> {
|
|
if me.ident.is_internal() {
|
|
trace!("Internal operation, not enforcing system object protection");
|
|
return Ok(());
|
|
}
|
|
|
|
me.modset
|
|
.values()
|
|
.flat_map(|ml| ml.iter())
|
|
.try_fold((), |(), m| match m {
|
|
Modify::Present(a, v) => {
|
|
if a == Attribute::Class.as_ref()
|
|
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
|
{
|
|
trace!("Rejecting operation during pre_batch_modify check");
|
|
Err(OperationError::SystemProtectedObject)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
_ => Ok(()),
|
|
})?;
|
|
|
|
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
|
// have some allowed attrs.
|
|
cand.iter().try_fold((), |(), cand| {
|
|
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
|
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
|
{
|
|
Err(OperationError::SystemProtectedObject)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})?;
|
|
|
|
// if class: system, check the mods are "allowed"
|
|
let system_pres = cand.iter().any(|c| {
|
|
// We don't need to check for domain info here because domain_info has a class
|
|
// system also. We just need to block it from being created.
|
|
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
|
});
|
|
|
|
trace!("{}: system -> {}", Attribute::Class, system_pres);
|
|
// No system types being altered, return.
|
|
if !system_pres {
|
|
return Ok(());
|
|
}
|
|
|
|
// Something altered is system, check if it's allowed.
|
|
me.modset
|
|
.values()
|
|
.flat_map(|ml| ml.iter())
|
|
.try_fold((), |(), m| {
|
|
// Already hit an error, move on.
|
|
let a = match m {
|
|
Modify::Present(a, _) | Modify::Removed(a, _) | Modify::Set(a, _) | Modify::Purged(a) => Some(a),
|
|
Modify::Assert(_, _) => None,
|
|
};
|
|
if let Some(attr) = a {
|
|
match ALLOWED_ATTRS.contains(attr) {
|
|
true => Ok(()),
|
|
false => {
|
|
|
|
trace!("Rejecting operation during pre_batch_modify check, if you're getting this check ALLOWED_ATTRS");
|
|
Err(OperationError::SystemProtectedObject)
|
|
},
|
|
}
|
|
} else {
|
|
// Was not a mod needing checking
|
|
Ok(())
|
|
}
|
|
})
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "protected_pre_delete", skip_all)]
|
|
fn pre_delete(
|
|
_qs: &mut QueryServerWriteTransaction,
|
|
// Should these be EntrySealed
|
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
|
de: &DeleteEvent,
|
|
) -> Result<(), OperationError> {
|
|
if de.ident.is_internal() {
|
|
trace!("Internal operation, not enforcing system object protection");
|
|
return Ok(());
|
|
}
|
|
|
|
cand.iter().try_fold((), |(), cand| {
|
|
if PROTECTED_ENTRYCLASSES
|
|
.iter()
|
|
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
|
{
|
|
trace!("Rejecting operation during pre_delete check");
|
|
Err(OperationError::SystemProtectedObject)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::prelude::*;
|
|
use std::sync::Arc;
|
|
|
|
const UUID_TEST_ACCOUNT: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
|
const UUID_TEST_GROUP: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
|
|
const UUID_TEST_ACP: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
|
|
|
|
lazy_static! {
|
|
pub static ref TEST_ACCOUNT: EntryInitNew = entry_init!(
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
|
(Attribute::Class, EntryClass::MemberOf.to_value()),
|
|
(Attribute::Name, Value::new_iname("test_account_1")),
|
|
(Attribute::DisplayName, Value::new_utf8s("test_account_1")),
|
|
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT)),
|
|
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP))
|
|
);
|
|
pub static ref TEST_GROUP: EntryInitNew = entry_init!(
|
|
(Attribute::Class, EntryClass::Group.to_value()),
|
|
(Attribute::Name, Value::new_iname("test_group_a")),
|
|
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP)),
|
|
(Attribute::Member, Value::Refer(UUID_TEST_ACCOUNT))
|
|
);
|
|
pub static ref ALLOW_ALL: EntryInitNew = entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(
|
|
Attribute::Class,
|
|
EntryClass::AccessControlProfile.to_value()
|
|
),
|
|
(
|
|
Attribute::Class,
|
|
EntryClass::AccessControlTargetScope.to_value()
|
|
),
|
|
(
|
|
Attribute::Class,
|
|
EntryClass::AccessControlReceiverGroup.to_value()
|
|
),
|
|
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
|
|
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
|
|
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),
|
|
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
|
|
(
|
|
Attribute::Name,
|
|
Value::new_iname("idm_admins_acp_allow_all_test")
|
|
),
|
|
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACP)),
|
|
(Attribute::AcpReceiverGroup, Value::Refer(UUID_TEST_GROUP)),
|
|
(
|
|
Attribute::AcpTargetScope,
|
|
Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
|
|
),
|
|
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
|
(Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
|
|
(Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
|
|
(Attribute::AcpSearchAttr, Value::new_iutf8("classname")),
|
|
(
|
|
Attribute::AcpSearchAttr,
|
|
Value::new_iutf8(Attribute::AttributeName.as_ref())
|
|
),
|
|
(Attribute::AcpModifyClass, EntryClass::System.to_value()),
|
|
(Attribute::AcpModifyClass, Value::new_iutf8("domain_info")),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::Class)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::DisplayName)
|
|
),
|
|
(Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::Must)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::DomainName)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::DomainDisplayName)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::DomainUuid)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::DomainSsid)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::FernetPrivateKeyStr)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::Es256PrivateKeyDer)
|
|
),
|
|
(
|
|
Attribute::AcpModifyRemovedAttr,
|
|
Value::from(Attribute::PrivateCookieKey)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::Class)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::DisplayName)
|
|
),
|
|
(Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::Must)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::DomainName)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::DomainDisplayName)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::DomainUuid)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::DomainSsid)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::FernetPrivateKeyStr)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::Es256PrivateKeyDer)
|
|
),
|
|
(
|
|
Attribute::AcpModifyPresentAttr,
|
|
Value::from(Attribute::PrivateCookieKey)
|
|
),
|
|
(Attribute::AcpCreateClass, EntryClass::Object.to_value()),
|
|
(Attribute::AcpCreateClass, EntryClass::Account.to_value()),
|
|
(Attribute::AcpCreateClass, EntryClass::Person.to_value()),
|
|
(Attribute::AcpCreateClass, EntryClass::System.to_value()),
|
|
(Attribute::AcpCreateClass, EntryClass::DomainInfo.to_value()),
|
|
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
|
(Attribute::AcpCreateAttr, EntryClass::Class.to_value(),),
|
|
(
|
|
Attribute::AcpCreateAttr,
|
|
Value::from(Attribute::Description),
|
|
),
|
|
(
|
|
Attribute::AcpCreateAttr,
|
|
Value::from(Attribute::DisplayName),
|
|
),
|
|
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainName),),
|
|
(
|
|
Attribute::AcpCreateAttr,
|
|
Value::from(Attribute::DomainDisplayName)
|
|
),
|
|
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainUuid)),
|
|
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainSsid)),
|
|
(Attribute::AcpCreateAttr, Value::from(Attribute::Uuid)),
|
|
(
|
|
Attribute::AcpCreateAttr,
|
|
Value::from(Attribute::FernetPrivateKeyStr)
|
|
),
|
|
(
|
|
Attribute::AcpCreateAttr,
|
|
Value::from(Attribute::Es256PrivateKeyDer)
|
|
),
|
|
(
|
|
Attribute::AcpCreateAttr,
|
|
Value::from(Attribute::PrivateCookieKey)
|
|
),
|
|
(Attribute::AcpCreateAttr, Value::from(Attribute::Version))
|
|
);
|
|
pub static ref PRELOAD: Vec<EntryInitNew> =
|
|
vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
|
|
pub static ref E_TEST_ACCOUNT: Arc<EntrySealedCommitted> =
|
|
Arc::new(TEST_ACCOUNT.clone().into_sealed_committed());
|
|
}
|
|
|
|
#[test]
|
|
fn test_pre_create_deny() {
|
|
// Test creating with class: system is rejected.
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Class, EntryClass::Person.to_value()),
|
|
(Attribute::Class, EntryClass::System.to_value()),
|
|
(Attribute::Name, Value::new_iname("testperson")),
|
|
(
|
|
Attribute::DisplayName,
|
|
Value::Utf8("testperson".to_string())
|
|
)
|
|
);
|
|
|
|
let create = vec![e];
|
|
let preload = PRELOAD.clone();
|
|
|
|
run_create_test!(
|
|
Err(OperationError::SystemProtectedObject),
|
|
preload,
|
|
create,
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pre_modify_system_deny() {
|
|
// Test modify of class to a system is denied
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Class, EntryClass::Person.to_value()),
|
|
(Attribute::Class, EntryClass::System.to_value()),
|
|
(Attribute::Name, Value::new_iname("testperson")),
|
|
(
|
|
Attribute::DisplayName,
|
|
Value::Utf8("testperson".to_string())
|
|
)
|
|
);
|
|
|
|
let mut preload = PRELOAD.clone();
|
|
preload.push(e);
|
|
|
|
run_modify_test!(
|
|
Err(OperationError::SystemProtectedObject),
|
|
preload,
|
|
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
|
modlist!([
|
|
m_purge(Attribute::DisplayName),
|
|
m_pres(Attribute::DisplayName, &Value::new_utf8s("system test")),
|
|
]),
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {},
|
|
|_| {}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pre_modify_class_add_deny() {
|
|
// Show that adding a system class is denied
|
|
// TODO: replace this with a `SchemaClass` object
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
|
(Attribute::ClassName, Value::new_iutf8("testclass")),
|
|
(
|
|
Attribute::Uuid,
|
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
|
),
|
|
(
|
|
Attribute::Description,
|
|
Value::Utf8("class test".to_string())
|
|
)
|
|
);
|
|
let mut preload = PRELOAD.clone();
|
|
preload.push(e);
|
|
|
|
run_modify_test!(
|
|
Ok(()),
|
|
preload,
|
|
filter!(f_eq(
|
|
Attribute::ClassName,
|
|
PartialValue::new_iutf8("testclass")
|
|
)),
|
|
modlist!([
|
|
m_pres(Attribute::May, &Value::from(Attribute::Name)),
|
|
m_pres(Attribute::Must, &Value::from(Attribute::Name)),
|
|
]),
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {},
|
|
|_| {}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pre_delete_deny() {
|
|
// Test deleting with class: system is rejected.
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
(Attribute::Class, EntryClass::Person.to_value()),
|
|
(Attribute::Class, EntryClass::System.to_value()),
|
|
(Attribute::Name, Value::new_iname("testperson")),
|
|
(
|
|
Attribute::DisplayName,
|
|
Value::Utf8("testperson".to_string())
|
|
)
|
|
);
|
|
|
|
let mut preload = PRELOAD.clone();
|
|
preload.push(e);
|
|
|
|
run_delete_test!(
|
|
Err(OperationError::SystemProtectedObject),
|
|
preload,
|
|
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_modify_domain() {
|
|
// Can edit *my* domain_ssid and domain_name
|
|
// Show that adding a system class is denied
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
|
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
|
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
|
(
|
|
Attribute::Description,
|
|
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
|
),
|
|
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
|
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
|
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
|
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
|
(Attribute::Version, Value::Uint32(1))
|
|
);
|
|
|
|
let mut preload = PRELOAD.clone();
|
|
preload.push(e);
|
|
|
|
run_modify_test!(
|
|
Ok(()),
|
|
preload,
|
|
filter!(f_eq(
|
|
Attribute::Name,
|
|
PartialValue::new_iname("domain_example.net.au")
|
|
)),
|
|
modlist!([
|
|
m_purge(Attribute::DomainSsid),
|
|
m_pres(Attribute::DomainSsid, &Value::new_utf8s("NewExampleWifi")),
|
|
]),
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {},
|
|
|_| {}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ext_create_domain() {
|
|
// can not add a domain_info type - note the lack of class: system
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
|
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
|
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
|
(
|
|
Attribute::Description,
|
|
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
|
),
|
|
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
|
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
|
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
|
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
|
(Attribute::Version, Value::Uint32(1))
|
|
);
|
|
|
|
let create = vec![e];
|
|
let preload = PRELOAD.clone();
|
|
|
|
run_create_test!(
|
|
Err(OperationError::SystemProtectedObject),
|
|
preload,
|
|
create,
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_delete_domain() {
|
|
// On the real thing we have a class: system, but to prove the point ...
|
|
let e = entry_init!(
|
|
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
|
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
|
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
|
(
|
|
Attribute::Description,
|
|
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
|
),
|
|
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
|
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
|
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
|
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
|
(Attribute::Version, Value::Uint32(1))
|
|
);
|
|
|
|
let mut preload = PRELOAD.clone();
|
|
preload.push(e);
|
|
|
|
run_delete_test!(
|
|
Err(OperationError::SystemProtectedObject),
|
|
preload,
|
|
filter!(f_eq(
|
|
Attribute::Name,
|
|
PartialValue::new_iname("domain_example.net.au")
|
|
)),
|
|
Some(E_TEST_ACCOUNT.clone()),
|
|
|_| {}
|
|
);
|
|
}
|
|
}
|