use std::sync::Arc;
use super::ChangeFlag;
use crate::plugins::Plugins;
use crate::prelude::*;
pub(crate) struct ModifyPartial<'a> {
pub norm_cand: Vec<Entry<EntrySealed, EntryCommitted>>,
pub pre_candidates: Vec<Arc<Entry<EntrySealed, EntryCommitted>>>,
pub me: &'a ModifyEvent,
}
impl<'a> QueryServerWriteTransaction<'a> {
#[instrument(level = "debug", skip_all)]
pub fn modify(&mut self, me: &ModifyEvent) -> Result<(), OperationError> {
let mp = self.modify_pre_apply(me)?;
if let Some(mp) = mp {
self.modify_apply(mp)
} else {
Ok(())
}
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn modify_pre_apply<'x>(
&mut self,
me: &'x ModifyEvent,
) -> Result<Option<ModifyPartial<'x>>, OperationError> {
trace!(?me);
if !me.ident.is_internal() {
security_info!(name = %me.ident, "modify initiator");
}
if me.modlist.is_empty() {
request_error!("modify: empty modify request");
return Err(OperationError::EmptyRequest);
}
let pre_candidates = self
.impersonate_search_valid(me.filter.clone(), me.filter_orig.clone(), &me.ident)
.map_err(|e| {
admin_error!("modify: error in pre-candidate selection {:?}", e);
e
})?;
if pre_candidates.is_empty() {
if me.ident.is_internal() {
trace!(
"modify_pre_apply: no candidates match filter ... continuing {:?}",
me.filter
);
return Ok(None);
} else {
request_error!(
"modify: no candidates match filter, failure {:?}",
me.filter
);
return Err(OperationError::NoMatchingEntries);
}
};
trace!("modify_pre_apply: pre_candidates -> {:?}", pre_candidates);
trace!("modify_pre_apply: modlist -> {:?}", me.modlist);
let access = self.get_accesscontrols();
let op_allow = access
.modify_allow_operation(me, &pre_candidates)
.map_err(|e| {
admin_error!("Unable to check modify access {:?}", e);
e
})?;
if !op_allow {
return Err(OperationError::AccessDenied);
}
let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
.iter()
.map(|er| {
er.as_ref()
.clone()
.invalidate(self.cid.clone(), &self.trim_cid)
})
.collect();
candidates.iter_mut().try_for_each(|er| {
er.apply_modlist(&me.modlist).inspect_err(|_e| {
error!("Modification failed for {:?}", er.get_uuid());
})
})?;
trace!("modify: candidates -> {:?}", candidates);
if std::iter::zip(
pre_candidates
.iter()
.map(|e| e.mask_recycled_ts().is_none()),
candidates.iter().map(|e| e.mask_recycled_ts().is_none()),
)
.any(|(a, b)| a != b)
{
admin_warn!("Refusing to apply modifications that are attempting to bypass replication state machine.");
return Err(OperationError::AccessDenied);
}
Plugins::run_pre_modify(self, &pre_candidates, &mut candidates, me).map_err(|e| {
admin_error!("Pre-Modify operation failed (plugin), {:?}", e);
e
})?;
let res: Result<Vec<EntrySealedCommitted>, OperationError> = candidates
.into_iter()
.map(|entry| {
entry
.validate(&self.schema)
.map_err(|e| {
admin_error!("Schema Violation in validation of modify_pre_apply {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|entry| entry.seal(&self.schema))
})
.collect();
let norm_cand: Vec<Entry<_, _>> = res?;
Ok(Some(ModifyPartial {
norm_cand,
pre_candidates,
me,
}))
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn modify_apply(&mut self, mp: ModifyPartial<'_>) -> Result<(), OperationError> {
let ModifyPartial {
norm_cand,
pre_candidates,
me,
} = mp;
self.be_txn
.modify(&self.cid, &pre_candidates, &norm_cand)
.map_err(|e| {
admin_error!("Modify operation failed (backend), {:?}", e);
e
})?;
Plugins::run_post_modify(self, &pre_candidates, &norm_cand, me).map_err(|e| {
admin_error!("Post-Modify operation failed (plugin), {:?}", e);
e
})?;
if !self.changed_flags.contains(ChangeFlag::SCHEMA)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
|| e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
})
{
self.changed_flags.insert(ChangeFlag::SCHEMA)
}
if !self.changed_flags.contains(ChangeFlag::ACP)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
})
{
self.changed_flags.insert(ChangeFlag::ACP)
}
if !self.changed_flags.contains(ChangeFlag::APPLICATION)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
{
self.changed_flags.insert(ChangeFlag::APPLICATION)
}
if !self.changed_flags.contains(ChangeFlag::OAUTH2)
&& norm_cand
.iter()
.zip(pre_candidates.iter().map(|e| e.as_ref()))
.any(|(post, pre)| {
(post.attribute_equality(
Attribute::Class,
&EntryClass::OAuth2ResourceServer.into(),
) || pre.attribute_equality(
Attribute::Class,
&EntryClass::OAuth2ResourceServer.into(),
)) && post
.entry_changed_excluding_attribute(Attribute::OAuth2Session, &self.cid)
})
{
self.changed_flags.insert(ChangeFlag::OAUTH2)
}
if !self.changed_flags.contains(ChangeFlag::DOMAIN)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
{
self.changed_flags.insert(ChangeFlag::DOMAIN)
}
if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
{
self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
}
if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
{
self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
}
if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
|| e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
})
{
self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
}
self.changed_uuid.extend(
norm_cand
.iter()
.map(|e| e.get_uuid())
.chain(pre_candidates.iter().map(|e| e.get_uuid())),
);
trace!(
changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
);
if me.ident.is_internal() {
trace!("Modify operation success");
} else {
admin_info!("Modify operation success");
}
Ok(())
}
}
impl<'a> QueryServerWriteTransaction<'a> {
#[instrument(level = "debug", skip_all)]
pub(crate) fn internal_search_writeable(
&mut self,
filter: &Filter<FilterInvalid>,
) -> Result<Vec<EntryTuple>, OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let se = SearchEvent::new_internal(f_valid);
self.search(&se).map(|vs| {
vs.into_iter()
.map(|e| {
let writeable = e
.as_ref()
.clone()
.invalidate(self.cid.clone(), &self.trim_cid);
(e, writeable)
})
.collect()
})
}
#[allow(clippy::needless_pass_by_value)]
#[instrument(level = "debug", skip_all)]
pub(crate) fn internal_apply_writable(
&mut self,
candidate_tuples: Vec<(Arc<EntrySealedCommitted>, EntryInvalidCommitted)>,
) -> Result<(), OperationError> {
if candidate_tuples.is_empty() {
return Ok(());
}
let (pre_candidates, candidates): (
Vec<Arc<EntrySealedCommitted>>,
Vec<EntryInvalidCommitted>,
) = candidate_tuples.into_iter().unzip();
let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
.into_iter()
.map(|e| {
e.validate(&self.schema)
.map_err(|e| {
admin_error!(
"Schema Violation in internal_apply_writable validate: {:?}",
e
);
OperationError::SchemaViolation(e)
})
.map(|e| e.seal(&self.schema))
})
.collect();
let norm_cand: Vec<Entry<_, _>> = res?;
if cfg!(debug_assertions) || cfg!(test) {
pre_candidates
.iter()
.zip(norm_cand.iter())
.try_for_each(|(pre, post)| {
if pre.get_uuid() == post.get_uuid() {
Ok(())
} else {
admin_error!("modify - cand sets not correctly aligned");
Err(OperationError::InvalidRequestState)
}
})?;
}
self.be_txn
.modify(&self.cid, &pre_candidates, &norm_cand)
.map_err(|e| {
admin_error!("Modify operation failed (backend), {:?}", e);
e
})?;
if !self.changed_flags.contains(ChangeFlag::SCHEMA)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
|| e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
})
{
self.changed_flags.insert(ChangeFlag::SCHEMA)
}
if !self.changed_flags.contains(ChangeFlag::ACP)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
})
{
self.changed_flags.insert(ChangeFlag::ACP)
}
if !self.changed_flags.contains(ChangeFlag::APPLICATION)
&& norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
{
self.changed_flags.insert(ChangeFlag::APPLICATION)
}
if !self.changed_flags.contains(ChangeFlag::OAUTH2)
&& norm_cand.iter().any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
})
{
self.changed_flags.insert(ChangeFlag::OAUTH2)
}
if !self.changed_flags.contains(ChangeFlag::DOMAIN)
&& norm_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
{
self.changed_flags.insert(ChangeFlag::DOMAIN)
}
if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
&& norm_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
{
self.changed_flags.insert(ChangeFlag::DOMAIN)
}
self.changed_uuid.extend(
norm_cand
.iter()
.map(|e| e.get_uuid())
.chain(pre_candidates.iter().map(|e| e.get_uuid())),
);
trace!(
changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
);
trace!("Modify operation success");
Ok(())
}
#[instrument(level = "debug", skip_all)]
pub fn internal_modify(
&mut self,
filter: &Filter<FilterInvalid>,
modlist: &ModifyList<ModifyInvalid>,
) -> Result<(), OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let m_valid = modlist
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let me = ModifyEvent::new_internal(f_valid, m_valid);
self.modify(&me)
}
pub fn internal_modify_uuid(
&mut self,
target_uuid: Uuid,
modlist: &ModifyList<ModifyInvalid>,
) -> Result<(), OperationError> {
let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let m_valid = modlist
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let me = ModifyEvent::new_internal(f_valid, m_valid);
self.modify(&me)
}
pub fn impersonate_modify_valid(
&mut self,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
m_valid: ModifyList<ModifyValid>,
event: &Identity,
) -> Result<(), OperationError> {
let me = ModifyEvent::new_impersonate(event, f_valid, f_intent_valid, m_valid);
self.modify(&me)
}
pub fn impersonate_modify(
&mut self,
filter: &Filter<FilterInvalid>,
filter_intent: &Filter<FilterInvalid>,
modlist: &ModifyList<ModifyInvalid>,
event: &Identity,
) -> Result<(), OperationError> {
let f_valid = filter.validate(self.get_schema()).map_err(|e| {
admin_error!("filter Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
admin_error!("f_intent Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
admin_error!("modlist Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
self.impersonate_modify_valid(f_valid, f_intent_valid, m_valid, event)
}
pub fn impersonate_modify_gen_event(
&mut self,
filter: &Filter<FilterInvalid>,
filter_intent: &Filter<FilterInvalid>,
modlist: &ModifyList<ModifyInvalid>,
event: &Identity,
) -> Result<ModifyEvent, OperationError> {
let f_valid = filter.validate(self.get_schema()).map_err(|e| {
admin_error!("filter Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
admin_error!("f_intent Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
admin_error!("modlist Schema Invalid {:?}", e);
OperationError::SchemaViolation(e)
})?;
Ok(ModifyEvent::new_impersonate(
event,
f_valid,
f_intent_valid,
m_valid,
))
}
}
#[cfg(test)]
mod tests {
use crate::credential::Credential;
use crate::prelude::*;
use kanidm_lib_crypto::CryptoPolicy;
#[qs_test]
async fn test_modify(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let e2 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson2")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
),
(Attribute::Description, Value::new_utf8s("testperson2")),
(Attribute::DisplayName, Value::new_utf8s("testperson2"))
);
let ce = CreateEvent::new_internal(vec![e1, e2]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let me_emp = ModifyEvent::new_internal_invalid(
filter!(f_pres(Attribute::Class)),
ModifyList::new_list(vec![]),
);
assert_eq!(
server_txn.modify(&me_emp),
Err(OperationError::EmptyRequest)
);
let me_nochg = ModifyEvent::new_impersonate_entry_ser(
BUILTIN_ACCOUNT_IDM_ADMIN.clone(),
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("flarbalgarble")
)),
ModifyList::new_list(vec![Modify::Present(
Attribute::Description,
Value::from("anusaosu"),
)]),
);
assert_eq!(
server_txn.modify(&me_nochg),
Err(OperationError::NoMatchingEntries)
);
let me_inv_m = ModifyEvent::new_internal_invalid(
filter!(f_pres(Attribute::Class)),
ModifyList::new_list(vec![Modify::Present(
Attribute::NonExist,
Value::from("anusaosu"),
)]),
);
assert!(
server_txn.modify(&me_inv_m)
== Err(OperationError::SchemaViolation(
SchemaError::InvalidAttribute(Attribute::NonExist.to_string())
))
);
let me_sin = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson2")
)),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Description),
Modify::Present(Attribute::Description, Value::from("anusaosu")),
]),
);
assert!(server_txn.modify(&me_sin).is_ok());
let me_mult = ModifyEvent::new_internal_invalid(
filter!(f_or!([
f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
])),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Description),
Modify::Present(Attribute::Description, Value::from("anusaosu")),
]),
);
assert!(server_txn.modify(&me_mult).is_ok());
assert!(server_txn.commit().is_ok());
}
#[qs_test]
async fn test_modify_assert(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let t_uuid = Uuid::new_v4();
let r_uuid = Uuid::new_v4();
assert!(server_txn
.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Uuid, Value::Uuid(t_uuid))
),])
.is_ok());
assert!(matches!(
server_txn.internal_modify_uuid(
t_uuid,
&ModifyList::new_list(vec![
m_assert(Attribute::Uuid, &PartialValue::Uuid(r_uuid)),
m_pres(Attribute::Description, &Value::Utf8("test".into()))
])
),
Err(OperationError::ModifyAssertionFailed)
));
assert!(server_txn
.internal_modify_uuid(
t_uuid,
&ModifyList::new_list(vec![
m_assert(Attribute::Uuid, &PartialValue::Uuid(t_uuid)),
m_pres(Attribute::Description, &Value::Utf8("test".into()))
])
)
.is_ok());
}
#[qs_test]
async fn test_modify_invalid_class(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let me_sin = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
ModifyList::new_list(vec![Modify::Present(
Attribute::Class,
EntryClass::SystemInfo.to_value(),
)]),
);
assert!(server_txn.modify(&me_sin).is_err());
let me_sin = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
ModifyList::new_list(vec![Modify::Present(
Attribute::Name,
Value::new_iname("testpersonx"),
)]),
);
assert!(server_txn.modify(&me_sin).is_err());
let me_sin = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
ModifyList::new_list(vec![
Modify::Present(Attribute::Class, EntryClass::SystemInfo.to_value()),
Modify::Present(Attribute::Version, Value::new_uint32(1)),
]),
);
assert!(server_txn.modify(&me_sin).is_ok());
let me_sin = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Name),
Modify::Present(Attribute::Name, Value::new_iname("testpersonx")),
]),
);
assert!(server_txn.modify(&me_sin).is_ok());
}
#[qs_test]
async fn test_modify_password_only(server: &QueryServer) {
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let p = CryptoPolicy::minimum();
let cred = Credential::new_password_only(&p, "test_password").unwrap();
let v_cred = Value::new_credential("primary", cred);
assert!(v_cred.validate());
let me_inv_m = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("testperson1")
)),
ModifyList::new_list(vec![Modify::Present(Attribute::PrimaryCredential, v_cred)]),
);
assert!(server_txn.modify(&me_inv_m).is_ok());
let test_ent = server_txn
.internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
.expect("failed");
let cred_ref = test_ent
.get_ava_single_credential(Attribute::PrimaryCredential)
.expect("Failed");
assert!(cred_ref.verify_password("test_password").unwrap());
}
#[qs_test]
async fn test_modify_name_self_write(server: &QueryServer) {
let user_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(user_uuid)),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
assert!(server_txn.internal_create(vec![e1]).is_ok());
let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
let me_inv_m = ModifyEvent::new_impersonate_identity(
user_ident,
filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Name),
Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
Modify::Purged(Attribute::DisplayName),
Modify::Present(
Attribute::DisplayName,
Value::Utf8("test_person_renamed".into()),
),
Modify::Purged(Attribute::LegalName),
Modify::Present(
Attribute::LegalName,
Value::Utf8("test_person_renamed".into()),
),
]),
);
assert!(server_txn.modify(&me_inv_m).is_ok());
let modify_remove_person = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Uuid,
PartialValue::Uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
)),
ModifyList::new_list(vec![Modify::Purged(Attribute::Member)]),
);
assert!(server_txn.modify(&modify_remove_person).is_ok());
let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
let me_inv_m = ModifyEvent::new_impersonate_identity(
user_ident,
filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
ModifyList::new_list(vec![
Modify::Purged(Attribute::Name),
Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
Modify::Purged(Attribute::DisplayName),
Modify::Present(
Attribute::DisplayName,
Value::Utf8("test_person_renamed".into()),
),
Modify::Purged(Attribute::LegalName),
Modify::Present(
Attribute::LegalName,
Value::Utf8("test_person_renamed".into()),
),
]),
);
assert_eq!(
server_txn.modify(&me_inv_m),
Err(OperationError::AccessDenied)
);
}
}