mirror of
https://github.com/kanidm/kanidm.git
synced 2025-06-07 00:27:46 +02:00
Remove the protected plugin
This commit is contained in:
parent
567fe7b259
commit
b2d127b9e5
|
@ -695,7 +695,6 @@ mod tests {
|
|||
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::DisplayName, Value::new_iname("testperson")),
|
||||
(
|
||||
|
@ -726,7 +725,6 @@ mod tests {
|
|||
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::DisplayName, Value::new_iname("testperson")),
|
||||
(
|
||||
|
|
|
@ -22,7 +22,6 @@ mod jwskeygen;
|
|||
mod keyobject;
|
||||
mod memberof;
|
||||
mod namehistory;
|
||||
mod protected;
|
||||
mod refint;
|
||||
mod session;
|
||||
mod spn;
|
||||
|
@ -44,6 +43,7 @@ trait Plugin {
|
|||
Err(OperationError::InvalidState)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn pre_create(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
// List of what we will commit that is valid?
|
||||
|
@ -243,13 +243,14 @@ impl Plugins {
|
|||
attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "plugins::run_pre_create", skip_all)]
|
||||
#[instrument(level = "trace", name = "plugins::run_pre_create", skip_all)]
|
||||
pub fn run_pre_create(
|
||||
qs: &mut QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryNew>],
|
||||
ce: &CreateEvent,
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
_cand: &[Entry<EntrySealed, EntryNew>],
|
||||
_ce: &CreateEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::pre_create(qs, cand, ce)
|
||||
// protected::Protected::pre_create(qs, cand, ce)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "plugins::run_post_create", skip_all)]
|
||||
|
@ -269,7 +270,7 @@ impl Plugins {
|
|||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
me: &ModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::pre_modify(qs, pre_cand, cand, me)?;
|
||||
// protected::Protected::pre_modify(qs, pre_cand, cand, me)?;
|
||||
base::Base::pre_modify(qs, pre_cand, cand, me)?;
|
||||
valuedeny::ValueDeny::pre_modify(qs, pre_cand, cand, me)?;
|
||||
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
|
||||
|
@ -305,7 +306,7 @@ impl Plugins {
|
|||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
me: &BatchModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
// protected::Protected::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
base::Base::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
valuedeny::ValueDeny::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
|
@ -340,7 +341,7 @@ impl Plugins {
|
|||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
de: &DeleteEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::pre_delete(qs, cand, de)?;
|
||||
// protected::Protected::pre_delete(qs, cand, de)?;
|
||||
memberof::MemberOf::pre_delete(qs, cand, de)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,690 +0,0 @@
|
|||
// 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()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use super::profiles::{
|
||||
AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
||||
use crate::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
|
@ -177,18 +178,18 @@ fn protected_filter_entry(ident: &Identity, entry: &Entry<EntryInit, EntryNew>)
|
|||
}
|
||||
IdentType::User(_) => {
|
||||
// Now check things ...
|
||||
|
||||
// For now we just block create on sync object
|
||||
if let Some(classes) = entry.get_ava_set(Attribute::Class) {
|
||||
if classes.contains(&EntryClass::SyncObject.into()) {
|
||||
// Block the mod
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
|
||||
// It's different, go ahead
|
||||
IResult::Ignore
|
||||
} else {
|
||||
// Block the mod, something is present
|
||||
security_access!("attempt to create with protected class type");
|
||||
IResult::Denied
|
||||
} else {
|
||||
IResult::Ignore
|
||||
}
|
||||
} else {
|
||||
// Nothing to check.
|
||||
// Nothing to check - this entry will fail to create anyway because it has
|
||||
// no classes
|
||||
IResult::Ignore
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::profiles::{
|
||||
AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -155,25 +156,27 @@ fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -
|
|||
IResult::Denied
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
// Now check things ...
|
||||
|
||||
// For now we just block create on sync object
|
||||
if let Some(classes) = entry.get_ava_set(Attribute::Class) {
|
||||
if classes.contains(&EntryClass::SyncObject.into()) {
|
||||
// Block the mod
|
||||
security_access!("attempt to delete with protected class type");
|
||||
return IResult::Denied;
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent deletion of entries that exist in the system controlled entry range.
|
||||
if entry.get_uuid() <= UUID_ANONYMOUS {
|
||||
security_access!("attempt to delete system builtin entry");
|
||||
return IResult::Denied;
|
||||
}
|
||||
|
||||
// Checks exhausted, no more input from us
|
||||
IResult::Ignore
|
||||
// Prevent deleting some protected types.
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
|
||||
// It's different, go ahead
|
||||
IResult::Ignore
|
||||
} else {
|
||||
// Block the mod, something is present
|
||||
security_access!("attempt to create with protected class type");
|
||||
IResult::Denied
|
||||
}
|
||||
} else {
|
||||
// Nothing to check - this entry will fail to create anyway because it has
|
||||
// no classes
|
||||
IResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ mod create;
|
|||
mod delete;
|
||||
mod modify;
|
||||
pub mod profiles;
|
||||
pub(self) mod protected;
|
||||
mod search;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -3424,4 +3425,157 @@ mod tests {
|
|||
// Finally test it!
|
||||
test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_protected_deny_create() {
|
||||
sketching::test_init();
|
||||
|
||||
let ev1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
);
|
||||
let r1_set = vec![ev1];
|
||||
|
||||
let ev2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
);
|
||||
|
||||
let r2_set = vec![ev2];
|
||||
|
||||
let ce_admin = CreateEvent::new_impersonate_identity(
|
||||
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
|
||||
vec![],
|
||||
);
|
||||
|
||||
let acp = AccessControlCreate::from_raw(
|
||||
"test_create",
|
||||
Uuid::new_v4(),
|
||||
// Apply to admin
|
||||
UUID_TEST_GROUP_1,
|
||||
// To create matching filter testperson
|
||||
// Can this be empty?
|
||||
filter_valid!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
// classes
|
||||
EntryClass::Account.into(),
|
||||
// attrs
|
||||
"class name uuid",
|
||||
);
|
||||
|
||||
// Test allowed to create
|
||||
test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
|
||||
// Test reject create (not allowed attr)
|
||||
test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_protected_deny_delete() {
|
||||
sketching::test_init();
|
||||
|
||||
let ev1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
let r1_set = vec![Arc::new(ev1)];
|
||||
|
||||
let ev2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
|
||||
let r2_set = vec![Arc::new(ev2)];
|
||||
|
||||
let de = DeleteEvent::new_impersonate_entry(
|
||||
E_TEST_ACCOUNT_1.clone(),
|
||||
filter_all!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
);
|
||||
|
||||
let acp = AccessControlDelete::from_raw(
|
||||
"test_delete",
|
||||
Uuid::new_v4(),
|
||||
// Apply to admin
|
||||
UUID_TEST_GROUP_1,
|
||||
// To delete testperson
|
||||
filter_valid!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
);
|
||||
|
||||
// Test allowed to delete
|
||||
test_acp_delete!(&de, vec![acp.clone()], &r1_set, true);
|
||||
// Test not allowed to delete
|
||||
test_acp_delete!(&de, vec![acp.clone()], &r2_set, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_protected_deny_modify() {
|
||||
sketching::test_init();
|
||||
|
||||
let ev1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
let r1_set = vec![Arc::new(ev1)];
|
||||
|
||||
let ev2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
|
||||
let r2_set = vec![Arc::new(ev2)];
|
||||
|
||||
// Allow name and class, class is account
|
||||
let acp_allow = AccessControlModify::from_raw(
|
||||
"test_modify_allow",
|
||||
Uuid::new_v4(),
|
||||
// Apply to admin
|
||||
UUID_TEST_GROUP_1,
|
||||
// To modify testperson
|
||||
filter_valid!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
// Allow pres disp name and class
|
||||
"displayname class",
|
||||
// Allow rem disp name and class
|
||||
"displayname class",
|
||||
// And the class allowed is system
|
||||
EntryClass::System.into(),
|
||||
);
|
||||
|
||||
let me_pres = ModifyEvent::new_impersonate_entry(
|
||||
E_TEST_ACCOUNT_1.clone(),
|
||||
filter_all!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
modlist!([m_pres(Attribute::DisplayName, &Value::new_utf8s("value"))]),
|
||||
);
|
||||
|
||||
// Test allowed pres
|
||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
|
||||
|
||||
// Test not allowed pres (due to system class)
|
||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::prelude::*;
|
||||
use hashbrown::HashMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::profiles::{
|
||||
AccessControlModify, AccessControlModifyResolved, AccessControlReceiverCondition,
|
||||
AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::{LOCKED_ENTRY_CLASSES, PROTECTED_MOD_ENTRY_CLASSES};
|
||||
use super::{AccessResult, AccessResultClass};
|
||||
use crate::prelude::*;
|
||||
use hashbrown::HashMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) enum ModifyResult<'a> {
|
||||
|
@ -27,6 +27,7 @@ pub(super) fn apply_modify_access<'a>(
|
|||
) -> ModifyResult<'a> {
|
||||
let mut denied = false;
|
||||
let mut grant = false;
|
||||
|
||||
let mut constrain_pres = BTreeSet::default();
|
||||
let mut allow_pres = BTreeSet::default();
|
||||
let mut constrain_rem = BTreeSet::default();
|
||||
|
@ -50,9 +51,21 @@ pub(super) fn apply_modify_access<'a>(
|
|||
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
|
||||
}
|
||||
|
||||
if !grant && !denied {
|
||||
// Check with protected if we should proceed.
|
||||
// Check with protected if we should proceed.
|
||||
match modify_protected_attrs(ident, entry) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Constrain(mut set) => {
|
||||
constrain_rem.extend(set.iter().cloned());
|
||||
constrain_pres.append(&mut set)
|
||||
}
|
||||
// Can't grant.
|
||||
AccessResult::Grant |
|
||||
// Can't allow
|
||||
AccessResult::Allow(_) |
|
||||
AccessResult::Ignore => {}
|
||||
}
|
||||
|
||||
if !grant && !denied {
|
||||
// If it's a sync entry, constrain it.
|
||||
match modify_sync_constrain(ident, entry, sync_agreements) {
|
||||
AccessResult::Denied => denied = true,
|
||||
|
@ -168,13 +181,18 @@ pub(super) fn apply_modify_access<'a>(
|
|||
allow_rem
|
||||
};
|
||||
|
||||
let allowed_cls = if !constrain_cls.is_empty() {
|
||||
let mut allowed_cls = if !constrain_cls.is_empty() {
|
||||
// bit_and
|
||||
&constrain_cls & &allow_cls
|
||||
} else {
|
||||
allow_cls
|
||||
};
|
||||
|
||||
// Deny these classes from being part of any addition or removal to an entry
|
||||
for protected_cls in PROTECTED_MOD_ENTRY_CLASSES.iter() {
|
||||
allowed_cls.remove(protected_cls.as_str());
|
||||
}
|
||||
|
||||
ModifyResult::Allow {
|
||||
pres: allowed_pres,
|
||||
rem: allowed_rem,
|
||||
|
@ -282,3 +300,94 @@ fn modify_sync_constrain(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify if the modification runs into limits that are defined by our protection rules.
|
||||
fn modify_protected_attrs(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> AccessResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => {
|
||||
// We don't constraint or influence these.
|
||||
AccessResult::Ignore
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
if classes.is_disjoint(&PROTECTED_MOD_ENTRY_CLASSES) {
|
||||
// Not protected, go ahead
|
||||
AccessResult::Ignore
|
||||
} else {
|
||||
// Okay, the entry is protected, apply the full ruleset.
|
||||
modify_protected_entry_attrs(classes)
|
||||
}
|
||||
} else {
|
||||
// Nothing to check - this entry will fail to modify anyway because it has
|
||||
// no classes
|
||||
AccessResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_protected_entry_attrs(classes: &BTreeSet<String>) -> AccessResult {
|
||||
// This is where the majority of the logic is - this contains the modification
|
||||
// rules as they apply.
|
||||
|
||||
// First check for the hard-deny rules.
|
||||
if !classes.is_disjoint(&LOCKED_ENTRY_CLASSES) {
|
||||
// Hard deny attribute modifications to these types.
|
||||
return AccessResult::Denied;
|
||||
}
|
||||
|
||||
let mut constrain_attrs = BTreeSet::default();
|
||||
|
||||
// Allows removal of the recycled class specifically on recycled entries.
|
||||
if classes.contains(EntryClass::Recycled.into()) {
|
||||
constrain_attrs.extend([Attribute::Class]);
|
||||
}
|
||||
|
||||
if classes.contains(EntryClass::ClassType.into()) {
|
||||
constrain_attrs.extend([Attribute::May, Attribute::Must]);
|
||||
}
|
||||
|
||||
if classes.contains(EntryClass::SystemConfig.into()) {
|
||||
constrain_attrs.extend([Attribute::BadlistPassword]);
|
||||
}
|
||||
|
||||
// Allow domain settings.
|
||||
if classes.contains(EntryClass::DomainInfo.into()) {
|
||||
constrain_attrs.extend([
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::FernetPrivateKeyStr,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::IdVerificationEcKey,
|
||||
Attribute::DeniedName,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::Image,
|
||||
]);
|
||||
}
|
||||
|
||||
// Allow account policy related attributes to be changed on dyngroup
|
||||
if classes.contains(EntryClass::DynGroup.into()) {
|
||||
constrain_attrs.extend([
|
||||
Attribute::AuthSessionExpiry,
|
||||
Attribute::AuthPasswordMinimumLength,
|
||||
Attribute::CredentialTypeMinimum,
|
||||
Attribute::PrivilegeExpiry,
|
||||
Attribute::WebauthnAttestationCaList,
|
||||
Attribute::LimitSearchMaxResults,
|
||||
Attribute::LimitSearchMaxFilterTest,
|
||||
Attribute::AllowPrimaryCredFallback,
|
||||
]);
|
||||
}
|
||||
|
||||
// If we don't constrain the attributes at all, we have to deny the change
|
||||
// from proceeding.
|
||||
if constrain_attrs.is_empty() {
|
||||
AccessResult::Denied
|
||||
} else {
|
||||
AccessResult::Constrain(constrain_attrs)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue