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::DOMAIN)
249 && norm_cand
250 .iter()
251 .chain(pre_candidates.iter().map(|e| e.as_ref()))
252 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
253 {
254 self.changed_flags.insert(ChangeFlag::DOMAIN)
255 }
256
257 if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
258 && norm_cand
259 .iter()
260 .chain(pre_candidates.iter().map(|e| e.as_ref()))
261 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
262 {
263 self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
264 }
265
266 if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
267 && norm_cand
268 .iter()
269 .chain(pre_candidates.iter().map(|e| e.as_ref()))
270 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
271 {
272 self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
273 }
274
275 if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
276 && norm_cand
277 .iter()
278 .chain(pre_candidates.iter().map(|e| e.as_ref()))
279 .any(|e| {
280 e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
281 || e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
282 })
283 {
284 self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
285 }
286
287 self.changed_uuid.extend(
288 norm_cand
289 .iter()
290 .map(|e| e.get_uuid())
291 .chain(pre_candidates.iter().map(|e| e.get_uuid())),
292 );
293
294 trace!(
295 changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
296 );
297
298 if me.ident.is_internal() {
300 trace!("Modify operation success");
301 } else {
302 admin_info!("Modify operation success");
303 }
304 Ok(())
305 }
306}
307
308impl QueryServerWriteTransaction<'_> {
309 #[instrument(level = "debug", skip_all)]
313 pub(crate) fn internal_search_writeable(
314 &mut self,
315 filter: &Filter<FilterInvalid>,
316 ) -> Result<Vec<EntryTuple>, OperationError> {
317 let f_valid = filter
318 .validate(self.get_schema())
319 .map_err(OperationError::SchemaViolation)?;
320 let se = SearchEvent::new_internal(f_valid);
321 self.search(&se).map(|vs| {
322 vs.into_iter()
323 .map(|e| {
324 let writeable = e
325 .as_ref()
326 .clone()
327 .invalidate(self.cid.clone(), &self.trim_cid);
328 (e, writeable)
329 })
330 .collect()
331 })
332 }
333
334 #[allow(clippy::needless_pass_by_value)]
340 #[instrument(level = "debug", skip_all)]
341 pub(crate) fn internal_apply_writable(
342 &mut self,
343 candidate_tuples: Vec<(Arc<EntrySealedCommitted>, EntryInvalidCommitted)>,
344 ) -> Result<(), OperationError> {
345 if candidate_tuples.is_empty() {
346 return Ok(());
348 }
349
350 let (pre_candidates, candidates): (
351 Vec<Arc<EntrySealedCommitted>>,
352 Vec<EntryInvalidCommitted>,
353 ) = candidate_tuples.into_iter().unzip();
354
355 let res: Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> = candidates
366 .into_iter()
367 .map(|e| {
368 e.validate(&self.schema)
369 .map_err(|e| {
370 admin_error!(
371 "Schema Violation in internal_apply_writable validate: {:?}",
372 e
373 );
374 OperationError::SchemaViolation(e)
375 })
376 .map(|e| e.seal(&self.schema))
377 })
378 .collect();
379
380 let norm_cand: Vec<Entry<_, _>> = res?;
381
382 if cfg!(debug_assertions) || cfg!(test) {
383 pre_candidates
384 .iter()
385 .zip(norm_cand.iter())
386 .try_for_each(|(pre, post)| {
387 if pre.get_uuid() == post.get_uuid() {
388 Ok(())
389 } else {
390 admin_error!("modify - cand sets not correctly aligned");
391 Err(OperationError::InvalidRequestState)
392 }
393 })?;
394 }
395
396 self.be_txn
398 .modify(&self.cid, &pre_candidates, &norm_cand)
399 .map_err(|e| {
400 admin_error!("Modify operation failed (backend), {:?}", e);
401 e
402 })?;
403
404 if !self.changed_flags.contains(ChangeFlag::SCHEMA)
405 && norm_cand
406 .iter()
407 .chain(pre_candidates.iter().map(|e| e.as_ref()))
408 .any(|e| {
409 e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
410 || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
411 })
412 {
413 self.changed_flags.insert(ChangeFlag::SCHEMA)
414 }
415
416 if !self.changed_flags.contains(ChangeFlag::ACP)
417 && norm_cand
418 .iter()
419 .chain(pre_candidates.iter().map(|e| e.as_ref()))
420 .any(|e| {
421 e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
422 })
423 {
424 self.changed_flags.insert(ChangeFlag::ACP)
425 }
426
427 if !self.changed_flags.contains(ChangeFlag::APPLICATION)
428 && norm_cand
429 .iter()
430 .chain(pre_candidates.iter().map(|e| e.as_ref()))
431 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
432 {
433 self.changed_flags.insert(ChangeFlag::APPLICATION)
434 }
435
436 if !self.changed_flags.contains(ChangeFlag::OAUTH2)
437 && norm_cand.iter().any(|e| {
438 e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
439 })
440 {
441 self.changed_flags.insert(ChangeFlag::OAUTH2)
442 }
443 if !self.changed_flags.contains(ChangeFlag::DOMAIN)
444 && norm_cand
445 .iter()
446 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
447 {
448 self.changed_flags.insert(ChangeFlag::DOMAIN)
449 }
450 if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
451 && norm_cand
452 .iter()
453 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
454 {
455 self.changed_flags.insert(ChangeFlag::DOMAIN)
456 }
457
458 self.changed_uuid.extend(
459 norm_cand
460 .iter()
461 .map(|e| e.get_uuid())
462 .chain(pre_candidates.iter().map(|e| e.get_uuid())),
463 );
464
465 trace!(
466 changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
467 );
468
469 trace!("Modify operation success");
470 Ok(())
471 }
472
473 #[instrument(level = "debug", skip_all)]
474 pub fn internal_modify(
475 &mut self,
476 filter: &Filter<FilterInvalid>,
477 modlist: &ModifyList<ModifyInvalid>,
478 ) -> Result<(), OperationError> {
479 let f_valid = filter
480 .validate(self.get_schema())
481 .map_err(OperationError::SchemaViolation)?;
482 let m_valid = modlist
483 .validate(self.get_schema())
484 .map_err(OperationError::SchemaViolation)?;
485 let me = ModifyEvent::new_internal(f_valid, m_valid);
486 self.modify(&me)
487 }
488
489 pub fn internal_modify_uuid(
490 &mut self,
491 target_uuid: Uuid,
492 modlist: &ModifyList<ModifyInvalid>,
493 ) -> Result<(), OperationError> {
494 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
495 let f_valid = filter
496 .validate(self.get_schema())
497 .map_err(OperationError::SchemaViolation)?;
498 let m_valid = modlist
499 .validate(self.get_schema())
500 .map_err(OperationError::SchemaViolation)?;
501 let me = ModifyEvent::new_internal(f_valid, m_valid);
502 self.modify(&me)
503 }
504
505 pub fn impersonate_modify_valid(
506 &mut self,
507 f_valid: Filter<FilterValid>,
508 f_intent_valid: Filter<FilterValid>,
509 m_valid: ModifyList<ModifyValid>,
510 event: &Identity,
511 ) -> Result<(), OperationError> {
512 let me = ModifyEvent::new_impersonate(event, f_valid, f_intent_valid, m_valid);
513 self.modify(&me)
514 }
515
516 pub fn impersonate_modify(
517 &mut self,
518 filter: &Filter<FilterInvalid>,
519 filter_intent: &Filter<FilterInvalid>,
520 modlist: &ModifyList<ModifyInvalid>,
521 event: &Identity,
522 ) -> Result<(), OperationError> {
523 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
524 admin_error!("filter Schema Invalid {:?}", e);
525 OperationError::SchemaViolation(e)
526 })?;
527 let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
528 admin_error!("f_intent Schema Invalid {:?}", e);
529 OperationError::SchemaViolation(e)
530 })?;
531 let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
532 admin_error!("modlist Schema Invalid {:?}", e);
533 OperationError::SchemaViolation(e)
534 })?;
535 self.impersonate_modify_valid(f_valid, f_intent_valid, m_valid, event)
536 }
537
538 pub fn impersonate_modify_gen_event(
539 &mut self,
540 filter: &Filter<FilterInvalid>,
541 filter_intent: &Filter<FilterInvalid>,
542 modlist: &ModifyList<ModifyInvalid>,
543 event: &Identity,
544 ) -> Result<ModifyEvent, OperationError> {
545 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
546 admin_error!("filter Schema Invalid {:?}", e);
547 OperationError::SchemaViolation(e)
548 })?;
549 let f_intent_valid = filter_intent.validate(self.get_schema()).map_err(|e| {
550 admin_error!("f_intent Schema Invalid {:?}", e);
551 OperationError::SchemaViolation(e)
552 })?;
553 let m_valid = modlist.validate(self.get_schema()).map_err(|e| {
554 admin_error!("modlist Schema Invalid {:?}", e);
555 OperationError::SchemaViolation(e)
556 })?;
557 Ok(ModifyEvent::new_impersonate(
558 event,
559 f_valid,
560 f_intent_valid,
561 m_valid,
562 ))
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use crate::credential::Credential;
569 use crate::prelude::*;
570 use kanidm_lib_crypto::CryptoPolicy;
571
572 #[qs_test]
573 async fn test_modify(server: &QueryServer) {
574 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
576
577 let e1 = entry_init!(
578 (Attribute::Class, EntryClass::Object.to_value()),
579 (Attribute::Class, EntryClass::Account.to_value()),
580 (Attribute::Class, EntryClass::Person.to_value()),
581 (Attribute::Name, Value::new_iname("testperson1")),
582 (
583 Attribute::Uuid,
584 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
585 ),
586 (Attribute::Description, Value::new_utf8s("testperson1")),
587 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
588 );
589
590 let e2 = entry_init!(
591 (Attribute::Class, EntryClass::Object.to_value()),
592 (Attribute::Class, EntryClass::Account.to_value()),
593 (Attribute::Class, EntryClass::Person.to_value()),
594 (Attribute::Name, Value::new_iname("testperson2")),
595 (
596 Attribute::Uuid,
597 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
598 ),
599 (Attribute::Description, Value::new_utf8s("testperson2")),
600 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
601 );
602
603 let ce = CreateEvent::new_internal(vec![e1, e2]);
604
605 let cr = server_txn.create(&ce);
606 assert!(cr.is_ok());
607
608 let me_emp = ModifyEvent::new_internal_invalid(
610 filter!(f_pres(Attribute::Class)),
611 ModifyList::new_list(vec![]),
612 );
613 assert_eq!(
614 server_txn.modify(&me_emp),
615 Err(OperationError::EmptyRequest)
616 );
617
618 let idm_admin = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
619
620 let me_nochg = ModifyEvent::new_impersonate_entry(
622 idm_admin,
623 filter!(f_eq(
624 Attribute::Name,
625 PartialValue::new_iname("flarbalgarble")
626 )),
627 ModifyList::new_list(vec![Modify::Present(
628 Attribute::Description,
629 Value::from("anusaosu"),
630 )]),
631 );
632 assert_eq!(
633 server_txn.modify(&me_nochg),
634 Err(OperationError::NoMatchingEntries)
635 );
636
637 let me_inv_m = ModifyEvent::new_internal_invalid(
661 filter!(f_pres(Attribute::Class)),
662 ModifyList::new_list(vec![Modify::Present(
663 Attribute::NonExist,
664 Value::from("anusaosu"),
665 )]),
666 );
667 assert!(
668 server_txn.modify(&me_inv_m)
669 == Err(OperationError::SchemaViolation(
670 SchemaError::InvalidAttribute(Attribute::NonExist.to_string())
671 ))
672 );
673
674 let me_sin = ModifyEvent::new_internal_invalid(
676 filter!(f_eq(
677 Attribute::Name,
678 PartialValue::new_iname("testperson2")
679 )),
680 ModifyList::new_list(vec![
681 Modify::Purged(Attribute::Description),
682 Modify::Present(Attribute::Description, Value::from("anusaosu")),
683 ]),
684 );
685 assert!(server_txn.modify(&me_sin).is_ok());
686
687 let me_mult = ModifyEvent::new_internal_invalid(
689 filter!(f_or!([
690 f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
691 f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
692 ])),
693 ModifyList::new_list(vec![
694 Modify::Purged(Attribute::Description),
695 Modify::Present(Attribute::Description, Value::from("anusaosu")),
696 ]),
697 );
698 assert!(server_txn.modify(&me_mult).is_ok());
699
700 assert!(server_txn.commit().is_ok());
701 }
702
703 #[qs_test]
704 async fn test_modify_assert(server: &QueryServer) {
705 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
706
707 let t_uuid = Uuid::new_v4();
708 let r_uuid = Uuid::new_v4();
709
710 assert!(server_txn
711 .internal_create(vec![entry_init!(
712 (Attribute::Class, EntryClass::Object.to_value()),
713 (Attribute::Uuid, Value::Uuid(t_uuid))
714 ),])
715 .is_ok());
716
717 assert!(matches!(
719 server_txn.internal_modify_uuid(
720 t_uuid,
721 &ModifyList::new_list(vec![
722 m_assert(Attribute::Uuid, &PartialValue::Uuid(r_uuid)),
723 m_pres(Attribute::Description, &Value::Utf8("test".into()))
724 ])
725 ),
726 Err(OperationError::ModifyAssertionFailed)
727 ));
728
729 assert!(server_txn
731 .internal_modify_uuid(
732 t_uuid,
733 &ModifyList::new_list(vec![
734 m_assert(Attribute::Uuid, &PartialValue::Uuid(t_uuid)),
735 m_pres(Attribute::Description, &Value::Utf8("test".into()))
736 ])
737 )
738 .is_ok());
739 }
740
741 #[qs_test]
742 async fn test_modify_invalid_class(server: &QueryServer) {
743 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
746
747 let e1 = entry_init!(
748 (Attribute::Class, EntryClass::Object.to_value()),
749 (Attribute::Class, EntryClass::Account.to_value()),
750 (Attribute::Class, EntryClass::Person.to_value()),
751 (Attribute::Name, Value::new_iname("testperson1")),
752 (
753 Attribute::Uuid,
754 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
755 ),
756 (Attribute::Description, Value::new_utf8s("testperson1")),
757 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
758 );
759
760 let ce = CreateEvent::new_internal(vec![e1]);
761
762 let cr = server_txn.create(&ce);
763 assert!(cr.is_ok());
764
765 let me_sin = ModifyEvent::new_internal_invalid(
767 filter!(f_eq(
768 Attribute::Name,
769 PartialValue::new_iname("testperson1")
770 )),
771 ModifyList::new_list(vec![Modify::Present(
772 Attribute::Class,
773 EntryClass::SystemInfo.to_value(),
774 )]),
775 );
776 assert!(server_txn.modify(&me_sin).is_err());
777
778 let me_sin = ModifyEvent::new_internal_invalid(
780 filter!(f_eq(
781 Attribute::Name,
782 PartialValue::new_iname("testperson1")
783 )),
784 ModifyList::new_list(vec![Modify::Present(
785 Attribute::Name,
786 Value::new_iname("testpersonx"),
787 )]),
788 );
789 assert!(server_txn.modify(&me_sin).is_err());
790
791 let me_sin = ModifyEvent::new_internal_invalid(
793 filter!(f_eq(
794 Attribute::Name,
795 PartialValue::new_iname("testperson1")
796 )),
797 ModifyList::new_list(vec![
798 Modify::Present(Attribute::Class, EntryClass::SystemInfo.to_value()),
799 Modify::Present(Attribute::Version, Value::new_uint32(1)),
801 ]),
802 );
803 assert!(server_txn.modify(&me_sin).is_ok());
804
805 let me_sin = ModifyEvent::new_internal_invalid(
807 filter!(f_eq(
808 Attribute::Name,
809 PartialValue::new_iname("testperson1")
810 )),
811 ModifyList::new_list(vec![
812 Modify::Purged(Attribute::Name),
813 Modify::Present(Attribute::Name, Value::new_iname("testpersonx")),
814 ]),
815 );
816 assert!(server_txn.modify(&me_sin).is_ok());
817 }
818
819 #[qs_test]
820 async fn test_modify_password_only(server: &QueryServer) {
821 let e1 = entry_init!(
822 (Attribute::Class, EntryClass::Object.to_value()),
823 (Attribute::Class, EntryClass::Person.to_value()),
824 (Attribute::Class, EntryClass::Account.to_value()),
825 (Attribute::Name, Value::new_iname("testperson1")),
826 (
827 Attribute::Uuid,
828 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
829 ),
830 (Attribute::Description, Value::new_utf8s("testperson1")),
831 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
832 );
833 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
834 let ce = CreateEvent::new_internal(vec![e1]);
837 let cr = server_txn.create(&ce);
838 assert!(cr.is_ok());
839
840 let p = CryptoPolicy::minimum();
842 let cred = Credential::new_password_only(&p, "test_password").unwrap();
843 let v_cred = Value::new_credential("primary", cred);
844 assert!(v_cred.validate());
845
846 let me_inv_m = ModifyEvent::new_internal_invalid(
848 filter!(f_eq(
849 Attribute::Name,
850 PartialValue::new_iname("testperson1")
851 )),
852 ModifyList::new_list(vec![Modify::Present(Attribute::PrimaryCredential, v_cred)]),
853 );
854 assert!(server_txn.modify(&me_inv_m).is_ok());
856
857 let test_ent = server_txn
859 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
860 .expect("failed");
861 let cred_ref = test_ent
863 .get_ava_single_credential(Attribute::PrimaryCredential)
864 .expect("Failed");
865 assert!(cred_ref.verify_password("test_password").unwrap());
867 }
868
869 #[qs_test]
870 async fn test_modify_name_self_write(server: &QueryServer) {
871 let user_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
872 let e1 = entry_init!(
873 (Attribute::Class, EntryClass::Object.to_value()),
874 (Attribute::Class, EntryClass::Person.to_value()),
875 (Attribute::Class, EntryClass::Account.to_value()),
876 (Attribute::Name, Value::new_iname("testperson1")),
877 (Attribute::Uuid, Value::Uuid(user_uuid)),
878 (Attribute::Description, Value::new_utf8s("testperson1")),
879 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
880 );
881 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
882
883 assert!(server_txn.internal_create(vec![e1]).is_ok());
884
885 let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
888
889 let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
890
891 let me_inv_m = ModifyEvent::new_impersonate_identity(
893 user_ident,
894 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
895 ModifyList::new_list(vec![
896 Modify::Purged(Attribute::Name),
897 Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
898 Modify::Purged(Attribute::DisplayName),
899 Modify::Present(
900 Attribute::DisplayName,
901 Value::Utf8("test_person_renamed".into()),
902 ),
903 Modify::Purged(Attribute::LegalName),
904 Modify::Present(
905 Attribute::LegalName,
906 Value::Utf8("test_person_renamed".into()),
907 ),
908 ]),
909 );
910
911 assert!(server_txn.modify(&me_inv_m).is_ok());
913
914 let modify_remove_person = ModifyEvent::new_internal_invalid(
916 filter!(f_eq(
917 Attribute::Uuid,
918 PartialValue::Uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
919 )),
920 ModifyList::new_list(vec![Modify::Purged(Attribute::Member)]),
921 );
922
923 assert!(server_txn.modify(&modify_remove_person).is_ok());
924
925 let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
927
928 let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
929
930 let me_inv_m = ModifyEvent::new_impersonate_identity(
931 user_ident,
932 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
933 ModifyList::new_list(vec![
934 Modify::Purged(Attribute::Name),
935 Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
936 Modify::Purged(Attribute::DisplayName),
937 Modify::Present(
938 Attribute::DisplayName,
939 Value::Utf8("test_person_renamed".into()),
940 ),
941 Modify::Purged(Attribute::LegalName),
942 Modify::Present(
943 Attribute::LegalName,
944 Value::Utf8("test_person_renamed".into()),
945 ),
946 ]),
947 );
948
949 assert_eq!(
951 server_txn.modify(&me_inv_m),
952 Err(OperationError::AccessDenied)
953 );
954 }
955}