1use std::sync::Arc;
2
3use super::ChangeFlag;
4use crate::plugins::Plugins;
5use crate::prelude::*;
6
7pub(crate) struct ModifyPartial<'a> {
8 pub norm_cand: Vec<Entry<EntrySealed, EntryCommitted>>,
9 pub pre_candidates: Vec<Arc<Entry<EntrySealed, EntryCommitted>>>,
10 pub me: &'a ModifyEvent,
11}
12
13impl QueryServerWriteTransaction<'_> {
14 #[instrument(level = "debug", skip_all)]
15 pub fn modify(&mut self, me: &ModifyEvent) -> Result<(), OperationError> {
16 let mp = self.modify_pre_apply(me)?;
17 if let Some(mp) = mp {
18 self.modify_apply(mp)
19 } else {
20 Ok(())
22 }
23 }
24
25 #[instrument(level = "debug", skip_all)]
29 pub(crate) fn modify_pre_apply<'x>(
30 &mut self,
31 me: &'x ModifyEvent,
32 ) -> Result<Option<ModifyPartial<'x>>, OperationError> {
33 trace!(?me);
34
35 if !me.ident.is_internal() {
39 security_info!(name = %me.ident, "modify initiator");
40 }
41
42 if me.modlist.is_empty() {
46 request_error!("modify: empty modify request");
47 return Err(OperationError::EmptyRequest);
48 }
49
50 let pre_candidates = self
58 .impersonate_search_valid(me.filter.clone(), me.filter_orig.clone(), &me.ident)
59 .map_err(|e| {
60 admin_error!("modify: error in pre-candidate selection {:?}", e);
61 e
62 })?;
63
64 if pre_candidates.is_empty() {
65 if me.ident.is_internal() {
66 trace!(
67 "modify_pre_apply: no candidates match filter ... continuing {:?}",
68 me.filter
69 );
70 return Ok(None);
71 } else {
72 request_error!(
73 "modify: no candidates match filter, failure {:?}",
74 me.filter
75 );
76 return Err(OperationError::NoMatchingEntries);
77 }
78 };
79
80 trace!("modify_pre_apply: pre_candidates -> {:?}", pre_candidates);
81 trace!("modify_pre_apply: modlist -> {:?}", me.modlist);
82
83 let access = self.get_accesscontrols();
86 let op_allow = access
87 .modify_allow_operation(me, &pre_candidates)
88 .map_err(|e| {
89 admin_error!("Unable to check modify access {:?}", e);
90 e
91 })?;
92 if !op_allow {
93 return Err(OperationError::AccessDenied);
94 }
95
96 let mut candidates: Vec<Entry<EntryInvalid, EntryCommitted>> = pre_candidates
100 .iter()
101 .map(|er| {
102 er.as_ref()
103 .clone()
104 .invalidate(self.cid.clone(), &self.trim_cid)
105 })
106 .collect();
107
108 candidates.iter_mut().try_for_each(|er| {
109 er.apply_modlist(&me.modlist).inspect_err(|_e| {
110 error!("Modification failed for {:?}", er.get_uuid());
111 })
112 })?;
113
114 trace!("modify: candidates -> {:?}", candidates);
115
116 if std::iter::zip(
118 pre_candidates
119 .iter()
120 .map(|e| e.mask_recycled_ts().is_none()),
121 candidates.iter().map(|e| e.mask_recycled_ts().is_none()),
122 )
123 .any(|(a, b)| a != b)
124 {
125 admin_warn!("Refusing to apply modifications that are attempting to bypass replication state machine.");
126 return Err(OperationError::AccessDenied);
127 }
128
129 Plugins::run_pre_modify(self, &pre_candidates, &mut candidates, me).map_err(|e| {
131 admin_error!("Pre-Modify operation failed (plugin), {:?}", e);
132 e
133 })?;
134
135 let res: Result<Vec<EntrySealedCommitted>, OperationError> = candidates
143 .into_iter()
144 .map(|entry| {
145 entry
146 .validate(&self.schema)
147 .map_err(|e| {
148 admin_error!("Schema Violation in validation of modify_pre_apply {:?}", e);
149 OperationError::SchemaViolation(e)
150 })
151 .map(|entry| entry.seal(&self.schema))
152 })
153 .collect();
154
155 let norm_cand: Vec<Entry<_, _>> = res?;
156
157 Ok(Some(ModifyPartial {
158 norm_cand,
159 pre_candidates,
160 me,
161 }))
162 }
163
164 #[instrument(level = "debug", skip_all)]
165 pub(crate) fn modify_apply(&mut self, mp: ModifyPartial<'_>) -> Result<(), OperationError> {
166 let ModifyPartial {
167 norm_cand,
168 pre_candidates,
169 me,
170 } = mp;
171
172 self.be_txn
174 .modify(&self.cid, &pre_candidates, &norm_cand)
175 .map_err(|e| {
176 admin_error!("Modify operation failed (backend), {:?}", e);
177 e
178 })?;
179
180 Plugins::run_post_modify(self, &pre_candidates, &norm_cand, me).map_err(|e| {
185 admin_error!("Post-Modify operation failed (plugin), {:?}", e);
186 e
187 })?;
188
189 if !self.changed_flags.contains(ChangeFlag::SCHEMA)
194 && norm_cand
195 .iter()
196 .chain(pre_candidates.iter().map(|e| e.as_ref()))
197 .any(|e| {
198 e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
199 || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
200 })
201 {
202 self.changed_flags.insert(ChangeFlag::SCHEMA)
203 }
204
205 if !self.changed_flags.contains(ChangeFlag::ACP)
206 && norm_cand
207 .iter()
208 .chain(pre_candidates.iter().map(|e| e.as_ref()))
209 .any(|e| {
210 e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
211 })
212 {
213 self.changed_flags.insert(ChangeFlag::ACP)
214 }
215
216 if !self.changed_flags.contains(ChangeFlag::APPLICATION)
217 && norm_cand
218 .iter()
219 .chain(pre_candidates.iter().map(|e| e.as_ref()))
220 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
221 {
222 self.changed_flags.insert(ChangeFlag::APPLICATION)
223 }
224
225 if !self.changed_flags.contains(ChangeFlag::OAUTH2)
226 && norm_cand
227 .iter()
228 .zip(pre_candidates.iter().map(|e| e.as_ref()))
229 .any(|(post, pre)| {
230 (post.attribute_equality(
236 Attribute::Class,
237 &EntryClass::OAuth2ResourceServer.into(),
238 ) || pre.attribute_equality(
239 Attribute::Class,
240 &EntryClass::OAuth2ResourceServer.into(),
241 )) && post
242 .entry_changed_excluding_attribute(Attribute::OAuth2Session, &self.cid)
243 })
244 {
245 self.changed_flags.insert(ChangeFlag::OAUTH2)
246 }
247
248 if !self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
249 && norm_cand
250 .iter()
251 .zip(pre_candidates.iter().map(|e| e.as_ref()))
252 .any(|(post, pre)| {
253 post.attribute_equality(Attribute::Class, &EntryClass::OAuth2Client.into())
254 || pre
255 .attribute_equality(Attribute::Class, &EntryClass::OAuth2Client.into())
256 })
257 {
258 self.changed_flags.insert(ChangeFlag::OAUTH2_CLIENT)
259 }
260
261 if !self.changed_flags.contains(ChangeFlag::FEATURE)
262 && norm_cand
263 .iter()
264 .zip(pre_candidates.iter().map(|e| e.as_ref()))
265 .any(|(post, pre)| {
266 post.attribute_equality(Attribute::Class, &EntryClass::Feature.into())
267 || pre.attribute_equality(Attribute::Class, &EntryClass::Feature.into())
268 })
269 {
270 self.changed_flags.insert(ChangeFlag::FEATURE)
271 }
272
273 if !self.changed_flags.contains(ChangeFlag::DOMAIN)
274 && norm_cand
275 .iter()
276 .chain(pre_candidates.iter().map(|e| e.as_ref()))
277 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
278 {
279 self.changed_flags.insert(ChangeFlag::DOMAIN)
280 }
281
282 if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
283 && norm_cand
284 .iter()
285 .chain(pre_candidates.iter().map(|e| e.as_ref()))
286 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
287 {
288 self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
289 }
290
291 if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
292 && norm_cand
293 .iter()
294 .chain(pre_candidates.iter().map(|e| e.as_ref()))
295 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
296 {
297 self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
298 }
299
300 if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
301 && norm_cand
302 .iter()
303 .chain(pre_candidates.iter().map(|e| e.as_ref()))
304 .any(|e| {
305 e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
306 || e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
307 })
308 {
309 self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
310 }
311
312 self.changed_uuid.extend(
313 norm_cand
314 .iter()
315 .map(|e| e.get_uuid())
316 .chain(pre_candidates.iter().map(|e| e.get_uuid())),
317 );
318
319 trace!(
320 changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
321 );
322
323 if me.ident.is_internal() {
325 trace!("Modify operation success");
326 } else {
327 admin_info!("Modify operation success");
328 }
329 Ok(())
330 }
331}
332
333impl QueryServerWriteTransaction<'_> {
334 #[instrument(level = "debug", skip_all)]
338 pub(crate) fn internal_search_writeable(
339 &mut self,
340 filter: &Filter<FilterInvalid>,
341 ) -> Result<Vec<EntryTuple>, OperationError> {
342 let f_valid = filter
343 .validate(self.get_schema())
344 .map_err(OperationError::SchemaViolation)?;
345 let se = SearchEvent::new_internal(f_valid);
346 self.search(&se).map(|vs| {
347 vs.into_iter()
348 .map(|e| {
349 let writeable = e
350 .as_ref()
351 .clone()
352 .invalidate(self.cid.clone(), &self.trim_cid);
353 (e, writeable)
354 })
355 .collect()
356 })
357 }
358
359 #[allow(clippy::needless_pass_by_value)]
365 #[instrument(level = "debug", skip_all)]
366 pub(crate) fn internal_apply_writable(
367 &mut self,
368 candidate_tuples: Vec<(Arc<EntrySealedCommitted>, EntryInvalidCommitted)>,
369 ) -> Result<(), OperationError> {
370 if candidate_tuples.is_empty() {
371 return Ok(());
373 }
374
375 let (pre_candidates, candidates): (
376 Vec<Arc<EntrySealedCommitted>>,
377 Vec<EntryInvalidCommitted>,
378 ) = candidate_tuples.into_iter().unzip();
379
380 let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
391 .into_iter()
392 .map(|e| {
393 e.validate(&self.schema)
394 .map_err(|e| {
395 admin_error!(
396 "Schema Violation in internal_apply_writable validate: {:?}",
397 e
398 );
399 OperationError::SchemaViolation(e)
400 })
401 .map(|e| e.seal(&self.schema))
402 })
403 .collect();
404
405 let norm_cand: Vec<Entry<_, _>> = res?;
406
407 if cfg!(debug_assertions) || cfg!(test) {
408 pre_candidates
409 .iter()
410 .zip(norm_cand.iter())
411 .try_for_each(|(pre, post)| {
412 if pre.get_uuid() == post.get_uuid() {
413 Ok(())
414 } else {
415 admin_error!("modify - cand sets not correctly aligned");
416 Err(OperationError::InvalidRequestState)
417 }
418 })?;
419 }
420
421 self.be_txn
423 .modify(&self.cid, &pre_candidates, &norm_cand)
424 .map_err(|e| {
425 admin_error!("Modify operation failed (backend), {:?}", e);
426 e
427 })?;
428
429 if !self.changed_flags.contains(ChangeFlag::SCHEMA)
430 && norm_cand
431 .iter()
432 .chain(pre_candidates.iter().map(|e| e.as_ref()))
433 .any(|e| {
434 e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
435 || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
436 })
437 {
438 self.changed_flags.insert(ChangeFlag::SCHEMA)
439 }
440
441 if !self.changed_flags.contains(ChangeFlag::ACP)
442 && norm_cand
443 .iter()
444 .chain(pre_candidates.iter().map(|e| e.as_ref()))
445 .any(|e| {
446 e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
447 })
448 {
449 self.changed_flags.insert(ChangeFlag::ACP)
450 }
451
452 if !self.changed_flags.contains(ChangeFlag::APPLICATION)
453 && norm_cand
454 .iter()
455 .chain(pre_candidates.iter().map(|e| e.as_ref()))
456 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
457 {
458 self.changed_flags.insert(ChangeFlag::APPLICATION)
459 }
460
461 if !self.changed_flags.contains(ChangeFlag::OAUTH2)
462 && norm_cand.iter().any(|e| {
463 e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
464 })
465 {
466 self.changed_flags.insert(ChangeFlag::OAUTH2)
467 }
468
469 if !self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
470 && norm_cand
471 .iter()
472 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::OAuth2Client.into()))
473 {
474 self.changed_flags.insert(ChangeFlag::OAUTH2_CLIENT)
475 }
476
477 if !self.changed_flags.contains(ChangeFlag::FEATURE)
478 && norm_cand
479 .iter()
480 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Feature.into()))
481 {
482 self.changed_flags.insert(ChangeFlag::FEATURE)
483 }
484
485 if !self.changed_flags.contains(ChangeFlag::DOMAIN)
486 && norm_cand
487 .iter()
488 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
489 {
490 self.changed_flags.insert(ChangeFlag::DOMAIN)
491 }
492 if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
493 && norm_cand
494 .iter()
495 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
496 {
497 self.changed_flags.insert(ChangeFlag::DOMAIN)
498 }
499
500 self.changed_uuid.extend(
501 norm_cand
502 .iter()
503 .map(|e| e.get_uuid())
504 .chain(pre_candidates.iter().map(|e| e.get_uuid())),
505 );
506
507 trace!(
508 changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
509 );
510
511 trace!("Modify operation success");
512 Ok(())
513 }
514
515 #[instrument(level = "debug", skip_all)]
516 pub fn internal_modify(
517 &mut self,
518 filter: &Filter<FilterInvalid>,
519 modlist: &ModifyList<ModifyInvalid>,
520 ) -> Result<(), OperationError> {
521 let f_valid = filter
522 .validate(self.get_schema())
523 .map_err(OperationError::SchemaViolation)?;
524 let m_valid = modlist
525 .validate(self.get_schema())
526 .map_err(OperationError::SchemaViolation)?;
527 let me = ModifyEvent::new_internal(f_valid, m_valid);
528 self.modify(&me)
529 }
530
531 pub fn internal_modify_uuid(
532 &mut self,
533 target_uuid: Uuid,
534 modlist: &ModifyList<ModifyInvalid>,
535 ) -> Result<(), OperationError> {
536 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
537 let f_valid = filter
538 .validate(self.get_schema())
539 .map_err(OperationError::SchemaViolation)?;
540 let m_valid = modlist
541 .validate(self.get_schema())
542 .map_err(OperationError::SchemaViolation)?;
543 let me = ModifyEvent::new_internal(f_valid, m_valid);
544 self.modify(&me)
545 }
546
547 pub fn impersonate_modify_valid(
548 &mut self,
549 f_valid: Filter<FilterValid>,
550 f_intent_valid: Filter<FilterValid>,
551 m_valid: ModifyList<ModifyValid>,
552 event: &Identity,
553 ) -> Result<(), OperationError> {
554 let me = ModifyEvent::new_impersonate(event, f_valid, f_intent_valid, m_valid);
555 self.modify(&me)
556 }
557
558 pub fn impersonate_modify(
559 &mut self,
560 filter: &Filter<FilterInvalid>,
561 filter_intent: &Filter<FilterInvalid>,
562 modlist: &ModifyList<ModifyInvalid>,
563 event: &Identity,
564 ) -> Result<(), OperationError> {
565 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
566 admin_error!("filter Schema Invalid {:?}", e);
567 OperationError::SchemaViolation(e)
568 })?;
569 let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
570 admin_error!("f_intent Schema Invalid {:?}", e);
571 OperationError::SchemaViolation(e)
572 })?;
573 let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
574 admin_error!("modlist Schema Invalid {:?}", e);
575 OperationError::SchemaViolation(e)
576 })?;
577 self.impersonate_modify_valid(f_valid, f_intent_valid, m_valid, event)
578 }
579
580 pub fn impersonate_modify_gen_event(
581 &mut self,
582 filter: &Filter<FilterInvalid>,
583 filter_intent: &Filter<FilterInvalid>,
584 modlist: &ModifyList<ModifyInvalid>,
585 event: &Identity,
586 ) -> Result<ModifyEvent, OperationError> {
587 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
588 admin_error!("filter Schema Invalid {:?}", e);
589 OperationError::SchemaViolation(e)
590 })?;
591 let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
592 admin_error!("f_intent Schema Invalid {:?}", e);
593 OperationError::SchemaViolation(e)
594 })?;
595 let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
596 admin_error!("modlist Schema Invalid {:?}", e);
597 OperationError::SchemaViolation(e)
598 })?;
599 Ok(ModifyEvent::new_impersonate(
600 event,
601 f_valid,
602 f_intent_valid,
603 m_valid,
604 ))
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use crate::credential::Credential;
611 use crate::prelude::*;
612 use kanidm_lib_crypto::CryptoPolicy;
613 use time::OffsetDateTime;
614
615 #[qs_test]
616 async fn test_modify(server: &QueryServer) {
617 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
619
620 let e1 = entry_init!(
621 (Attribute::Class, EntryClass::Object.to_value()),
622 (Attribute::Class, EntryClass::Account.to_value()),
623 (Attribute::Class, EntryClass::Person.to_value()),
624 (Attribute::Name, Value::new_iname("testperson1")),
625 (
626 Attribute::Uuid,
627 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
628 ),
629 (Attribute::Description, Value::new_utf8s("testperson1")),
630 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
631 );
632
633 let e2 = entry_init!(
634 (Attribute::Class, EntryClass::Object.to_value()),
635 (Attribute::Class, EntryClass::Account.to_value()),
636 (Attribute::Class, EntryClass::Person.to_value()),
637 (Attribute::Name, Value::new_iname("testperson2")),
638 (
639 Attribute::Uuid,
640 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
641 ),
642 (Attribute::Description, Value::new_utf8s("testperson2")),
643 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
644 );
645
646 let ce = CreateEvent::new_internal(vec![e1, e2]);
647
648 let cr = server_txn.create(&ce);
649 assert!(cr.is_ok());
650
651 let me_emp = ModifyEvent::new_internal_invalid(
653 filter!(f_pres(Attribute::Class)),
654 ModifyList::new_list(vec![]),
655 );
656 assert_eq!(
657 server_txn.modify(&me_emp),
658 Err(OperationError::EmptyRequest)
659 );
660
661 let idm_admin = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
662
663 let me_nochg = ModifyEvent::new_impersonate_entry(
665 idm_admin,
666 filter!(f_eq(
667 Attribute::Name,
668 PartialValue::new_iname("flarbalgarble")
669 )),
670 ModifyList::new_list(vec![Modify::Present(
671 Attribute::Description,
672 Value::from("anusaosu"),
673 )]),
674 );
675 assert_eq!(
676 server_txn.modify(&me_nochg),
677 Err(OperationError::NoMatchingEntries)
678 );
679
680 let me_inv_m = ModifyEvent::new_internal_invalid(
704 filter!(f_pres(Attribute::Class)),
705 ModifyList::new_list(vec![Modify::Present(
706 Attribute::NonExist,
707 Value::from("anusaosu"),
708 )]),
709 );
710 assert!(
711 server_txn.modify(&me_inv_m)
712 == Err(OperationError::SchemaViolation(
713 SchemaError::InvalidAttribute(Attribute::NonExist.to_string())
714 ))
715 );
716
717 let me_sin = ModifyEvent::new_internal_invalid(
719 filter!(f_eq(
720 Attribute::Name,
721 PartialValue::new_iname("testperson2")
722 )),
723 ModifyList::new_list(vec![
724 Modify::Purged(Attribute::Description),
725 Modify::Present(Attribute::Description, Value::from("anusaosu")),
726 ]),
727 );
728 assert!(server_txn.modify(&me_sin).is_ok());
729
730 let me_mult = ModifyEvent::new_internal_invalid(
732 filter!(f_or!([
733 f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
734 f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
735 ])),
736 ModifyList::new_list(vec![
737 Modify::Purged(Attribute::Description),
738 Modify::Present(Attribute::Description, Value::from("anusaosu")),
739 ]),
740 );
741 assert!(server_txn.modify(&me_mult).is_ok());
742
743 assert!(server_txn.commit().is_ok());
744 }
745
746 #[qs_test]
747 async fn test_modify_assert(server: &QueryServer) {
748 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
749
750 let t_uuid = Uuid::new_v4();
751 let r_uuid = Uuid::new_v4();
752
753 assert!(server_txn
754 .internal_create(vec![entry_init!(
755 (Attribute::Class, EntryClass::Object.to_value()),
756 (Attribute::Uuid, Value::Uuid(t_uuid))
757 ),])
758 .is_ok());
759
760 assert!(matches!(
762 server_txn.internal_modify_uuid(
763 t_uuid,
764 &ModifyList::new_list(vec![
765 m_assert(Attribute::Uuid, &PartialValue::Uuid(r_uuid)),
766 m_pres(Attribute::Description, &Value::Utf8("test".into()))
767 ])
768 ),
769 Err(OperationError::ModifyAssertionFailed)
770 ));
771
772 assert!(server_txn
774 .internal_modify_uuid(
775 t_uuid,
776 &ModifyList::new_list(vec![
777 m_assert(Attribute::Uuid, &PartialValue::Uuid(t_uuid)),
778 m_pres(Attribute::Description, &Value::Utf8("test".into()))
779 ])
780 )
781 .is_ok());
782 }
783
784 #[qs_test]
785 async fn test_modify_invalid_class(server: &QueryServer) {
786 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
789
790 let e1 = entry_init!(
791 (Attribute::Class, EntryClass::Object.to_value()),
792 (Attribute::Class, EntryClass::Account.to_value()),
793 (Attribute::Class, EntryClass::Person.to_value()),
794 (Attribute::Name, Value::new_iname("testperson1")),
795 (
796 Attribute::Uuid,
797 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
798 ),
799 (Attribute::Description, Value::new_utf8s("testperson1")),
800 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
801 );
802
803 let ce = CreateEvent::new_internal(vec![e1]);
804
805 let cr = server_txn.create(&ce);
806 assert!(cr.is_ok());
807
808 let me_sin = ModifyEvent::new_internal_invalid(
810 filter!(f_eq(
811 Attribute::Name,
812 PartialValue::new_iname("testperson1")
813 )),
814 ModifyList::new_list(vec![Modify::Present(
815 Attribute::Class,
816 EntryClass::SystemInfo.to_value(),
817 )]),
818 );
819 assert!(server_txn.modify(&me_sin).is_err());
820
821 let me_sin = ModifyEvent::new_internal_invalid(
823 filter!(f_eq(
824 Attribute::Name,
825 PartialValue::new_iname("testperson1")
826 )),
827 ModifyList::new_list(vec![Modify::Present(
828 Attribute::Name,
829 Value::new_iname("testpersonx"),
830 )]),
831 );
832 assert!(server_txn.modify(&me_sin).is_err());
833
834 let me_sin = ModifyEvent::new_internal_invalid(
836 filter!(f_eq(
837 Attribute::Name,
838 PartialValue::new_iname("testperson1")
839 )),
840 ModifyList::new_list(vec![
841 Modify::Present(Attribute::Class, EntryClass::SystemInfo.to_value()),
842 Modify::Present(Attribute::Version, Value::new_uint32(1)),
844 ]),
845 );
846 assert!(server_txn.modify(&me_sin).is_ok());
847
848 let me_sin = ModifyEvent::new_internal_invalid(
850 filter!(f_eq(
851 Attribute::Name,
852 PartialValue::new_iname("testperson1")
853 )),
854 ModifyList::new_list(vec![
855 Modify::Purged(Attribute::Name),
856 Modify::Present(Attribute::Name, Value::new_iname("testpersonx")),
857 ]),
858 );
859 assert!(server_txn.modify(&me_sin).is_ok());
860 }
861
862 #[qs_test]
863 async fn test_modify_password_only(server: &QueryServer) {
864 let e1 = entry_init!(
865 (Attribute::Class, EntryClass::Object.to_value()),
866 (Attribute::Class, EntryClass::Person.to_value()),
867 (Attribute::Class, EntryClass::Account.to_value()),
868 (Attribute::Name, Value::new_iname("testperson1")),
869 (
870 Attribute::Uuid,
871 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
872 ),
873 (Attribute::Description, Value::new_utf8s("testperson1")),
874 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
875 );
876 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
877 let ce = CreateEvent::new_internal(vec![e1]);
880 let cr = server_txn.create(&ce);
881 assert!(cr.is_ok());
882
883 let p = CryptoPolicy::minimum();
885 let cred =
886 Credential::new_password_only(&p, "test_password", OffsetDateTime::UNIX_EPOCH).unwrap();
887 let v_cred = Value::new_credential("primary", cred);
888 assert!(v_cred.validate());
889
890 let me_inv_m = ModifyEvent::new_internal_invalid(
892 filter!(f_eq(
893 Attribute::Name,
894 PartialValue::new_iname("testperson1")
895 )),
896 ModifyList::new_list(vec![Modify::Present(Attribute::PrimaryCredential, v_cred)]),
897 );
898 assert!(server_txn.modify(&me_inv_m).is_ok());
900
901 let test_ent = server_txn
903 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
904 .expect("failed");
905 let cred_ref = test_ent
907 .get_ava_single_credential(Attribute::PrimaryCredential)
908 .expect("Failed");
909 assert!(cred_ref.verify_password("test_password").unwrap());
911 }
912
913 #[qs_test]
914 async fn test_modify_name_self_write(server: &QueryServer) {
915 let user_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
916 let e1 = entry_init!(
917 (Attribute::Class, EntryClass::Object.to_value()),
918 (Attribute::Class, EntryClass::Person.to_value()),
919 (Attribute::Class, EntryClass::Account.to_value()),
920 (Attribute::Name, Value::new_iname("testperson1")),
921 (Attribute::Uuid, Value::Uuid(user_uuid)),
922 (Attribute::Description, Value::new_utf8s("testperson1")),
923 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
924 );
925 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
926
927 assert!(server_txn.internal_create(vec![e1]).is_ok());
928
929 let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
932
933 let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
934
935 let me_inv_m = ModifyEvent::new_impersonate_identity(
937 user_ident,
938 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
939 ModifyList::new_list(vec![
940 Modify::Purged(Attribute::Name),
941 Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
942 Modify::Purged(Attribute::DisplayName),
943 Modify::Present(
944 Attribute::DisplayName,
945 Value::Utf8("test_person_renamed".into()),
946 ),
947 Modify::Purged(Attribute::LegalName),
948 Modify::Present(
949 Attribute::LegalName,
950 Value::Utf8("test_person_renamed".into()),
951 ),
952 ]),
953 );
954
955 assert!(server_txn.modify(&me_inv_m).is_ok());
957
958 let modify_remove_person = ModifyEvent::new_internal_invalid(
960 filter!(f_eq(
961 Attribute::Uuid,
962 PartialValue::Uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
963 )),
964 ModifyList::new_list(vec![Modify::Purged(Attribute::Member)]),
965 );
966
967 assert!(server_txn.modify(&modify_remove_person).is_ok());
968
969 let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
971
972 let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
973
974 let me_inv_m = ModifyEvent::new_impersonate_identity(
975 user_ident,
976 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
977 ModifyList::new_list(vec![
978 Modify::Purged(Attribute::Name),
979 Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
980 Modify::Purged(Attribute::DisplayName),
981 Modify::Present(
982 Attribute::DisplayName,
983 Value::Utf8("test_person_renamed".into()),
984 ),
985 Modify::Purged(Attribute::LegalName),
986 Modify::Present(
987 Attribute::LegalName,
988 Value::Utf8("test_person_renamed".into()),
989 ),
990 ]),
991 );
992
993 assert_eq!(
995 server_txn.modify(&me_inv_m),
996 Err(OperationError::AccessDenied)
997 );
998 }
999}