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
614 #[qs_test]
615 async fn test_modify(server: &QueryServer) {
616 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
618
619 let e1 = entry_init!(
620 (Attribute::Class, EntryClass::Object.to_value()),
621 (Attribute::Class, EntryClass::Account.to_value()),
622 (Attribute::Class, EntryClass::Person.to_value()),
623 (Attribute::Name, Value::new_iname("testperson1")),
624 (
625 Attribute::Uuid,
626 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
627 ),
628 (Attribute::Description, Value::new_utf8s("testperson1")),
629 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
630 );
631
632 let e2 = entry_init!(
633 (Attribute::Class, EntryClass::Object.to_value()),
634 (Attribute::Class, EntryClass::Account.to_value()),
635 (Attribute::Class, EntryClass::Person.to_value()),
636 (Attribute::Name, Value::new_iname("testperson2")),
637 (
638 Attribute::Uuid,
639 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63932"))
640 ),
641 (Attribute::Description, Value::new_utf8s("testperson2")),
642 (Attribute::DisplayName, Value::new_utf8s("testperson2"))
643 );
644
645 let ce = CreateEvent::new_internal(vec![e1, e2]);
646
647 let cr = server_txn.create(&ce);
648 assert!(cr.is_ok());
649
650 let me_emp = ModifyEvent::new_internal_invalid(
652 filter!(f_pres(Attribute::Class)),
653 ModifyList::new_list(vec![]),
654 );
655 assert_eq!(
656 server_txn.modify(&me_emp),
657 Err(OperationError::EmptyRequest)
658 );
659
660 let idm_admin = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
661
662 let me_nochg = ModifyEvent::new_impersonate_entry(
664 idm_admin,
665 filter!(f_eq(
666 Attribute::Name,
667 PartialValue::new_iname("flarbalgarble")
668 )),
669 ModifyList::new_list(vec![Modify::Present(
670 Attribute::Description,
671 Value::from("anusaosu"),
672 )]),
673 );
674 assert_eq!(
675 server_txn.modify(&me_nochg),
676 Err(OperationError::NoMatchingEntries)
677 );
678
679 let me_inv_m = ModifyEvent::new_internal_invalid(
703 filter!(f_pres(Attribute::Class)),
704 ModifyList::new_list(vec![Modify::Present(
705 Attribute::NonExist,
706 Value::from("anusaosu"),
707 )]),
708 );
709 assert!(
710 server_txn.modify(&me_inv_m)
711 == Err(OperationError::SchemaViolation(
712 SchemaError::InvalidAttribute(Attribute::NonExist.to_string())
713 ))
714 );
715
716 let me_sin = ModifyEvent::new_internal_invalid(
718 filter!(f_eq(
719 Attribute::Name,
720 PartialValue::new_iname("testperson2")
721 )),
722 ModifyList::new_list(vec![
723 Modify::Purged(Attribute::Description),
724 Modify::Present(Attribute::Description, Value::from("anusaosu")),
725 ]),
726 );
727 assert!(server_txn.modify(&me_sin).is_ok());
728
729 let me_mult = ModifyEvent::new_internal_invalid(
731 filter!(f_or!([
732 f_eq(Attribute::Name, PartialValue::new_iname("testperson1")),
733 f_eq(Attribute::Name, PartialValue::new_iname("testperson2")),
734 ])),
735 ModifyList::new_list(vec![
736 Modify::Purged(Attribute::Description),
737 Modify::Present(Attribute::Description, Value::from("anusaosu")),
738 ]),
739 );
740 assert!(server_txn.modify(&me_mult).is_ok());
741
742 assert!(server_txn.commit().is_ok());
743 }
744
745 #[qs_test]
746 async fn test_modify_assert(server: &QueryServer) {
747 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
748
749 let t_uuid = Uuid::new_v4();
750 let r_uuid = Uuid::new_v4();
751
752 assert!(server_txn
753 .internal_create(vec![entry_init!(
754 (Attribute::Class, EntryClass::Object.to_value()),
755 (Attribute::Uuid, Value::Uuid(t_uuid))
756 ),])
757 .is_ok());
758
759 assert!(matches!(
761 server_txn.internal_modify_uuid(
762 t_uuid,
763 &ModifyList::new_list(vec![
764 m_assert(Attribute::Uuid, &PartialValue::Uuid(r_uuid)),
765 m_pres(Attribute::Description, &Value::Utf8("test".into()))
766 ])
767 ),
768 Err(OperationError::ModifyAssertionFailed)
769 ));
770
771 assert!(server_txn
773 .internal_modify_uuid(
774 t_uuid,
775 &ModifyList::new_list(vec![
776 m_assert(Attribute::Uuid, &PartialValue::Uuid(t_uuid)),
777 m_pres(Attribute::Description, &Value::Utf8("test".into()))
778 ])
779 )
780 .is_ok());
781 }
782
783 #[qs_test]
784 async fn test_modify_invalid_class(server: &QueryServer) {
785 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
788
789 let e1 = entry_init!(
790 (Attribute::Class, EntryClass::Object.to_value()),
791 (Attribute::Class, EntryClass::Account.to_value()),
792 (Attribute::Class, EntryClass::Person.to_value()),
793 (Attribute::Name, Value::new_iname("testperson1")),
794 (
795 Attribute::Uuid,
796 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
797 ),
798 (Attribute::Description, Value::new_utf8s("testperson1")),
799 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
800 );
801
802 let ce = CreateEvent::new_internal(vec![e1]);
803
804 let cr = server_txn.create(&ce);
805 assert!(cr.is_ok());
806
807 let me_sin = ModifyEvent::new_internal_invalid(
809 filter!(f_eq(
810 Attribute::Name,
811 PartialValue::new_iname("testperson1")
812 )),
813 ModifyList::new_list(vec![Modify::Present(
814 Attribute::Class,
815 EntryClass::SystemInfo.to_value(),
816 )]),
817 );
818 assert!(server_txn.modify(&me_sin).is_err());
819
820 let me_sin = ModifyEvent::new_internal_invalid(
822 filter!(f_eq(
823 Attribute::Name,
824 PartialValue::new_iname("testperson1")
825 )),
826 ModifyList::new_list(vec![Modify::Present(
827 Attribute::Name,
828 Value::new_iname("testpersonx"),
829 )]),
830 );
831 assert!(server_txn.modify(&me_sin).is_err());
832
833 let me_sin = ModifyEvent::new_internal_invalid(
835 filter!(f_eq(
836 Attribute::Name,
837 PartialValue::new_iname("testperson1")
838 )),
839 ModifyList::new_list(vec![
840 Modify::Present(Attribute::Class, EntryClass::SystemInfo.to_value()),
841 Modify::Present(Attribute::Version, Value::new_uint32(1)),
843 ]),
844 );
845 assert!(server_txn.modify(&me_sin).is_ok());
846
847 let me_sin = ModifyEvent::new_internal_invalid(
849 filter!(f_eq(
850 Attribute::Name,
851 PartialValue::new_iname("testperson1")
852 )),
853 ModifyList::new_list(vec![
854 Modify::Purged(Attribute::Name),
855 Modify::Present(Attribute::Name, Value::new_iname("testpersonx")),
856 ]),
857 );
858 assert!(server_txn.modify(&me_sin).is_ok());
859 }
860
861 #[qs_test]
862 async fn test_modify_password_only(server: &QueryServer) {
863 let e1 = entry_init!(
864 (Attribute::Class, EntryClass::Object.to_value()),
865 (Attribute::Class, EntryClass::Person.to_value()),
866 (Attribute::Class, EntryClass::Account.to_value()),
867 (Attribute::Name, Value::new_iname("testperson1")),
868 (
869 Attribute::Uuid,
870 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
871 ),
872 (Attribute::Description, Value::new_utf8s("testperson1")),
873 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
874 );
875 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
876 let ce = CreateEvent::new_internal(vec![e1]);
879 let cr = server_txn.create(&ce);
880 assert!(cr.is_ok());
881
882 let p = CryptoPolicy::minimum();
884 let cred = Credential::new_password_only(&p, "test_password").unwrap();
885 let v_cred = Value::new_credential("primary", cred);
886 assert!(v_cred.validate());
887
888 let me_inv_m = ModifyEvent::new_internal_invalid(
890 filter!(f_eq(
891 Attribute::Name,
892 PartialValue::new_iname("testperson1")
893 )),
894 ModifyList::new_list(vec![Modify::Present(Attribute::PrimaryCredential, v_cred)]),
895 );
896 assert!(server_txn.modify(&me_inv_m).is_ok());
898
899 let test_ent = server_txn
901 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
902 .expect("failed");
903 let cred_ref = test_ent
905 .get_ava_single_credential(Attribute::PrimaryCredential)
906 .expect("Failed");
907 assert!(cred_ref.verify_password("test_password").unwrap());
909 }
910
911 #[qs_test]
912 async fn test_modify_name_self_write(server: &QueryServer) {
913 let user_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
914 let e1 = entry_init!(
915 (Attribute::Class, EntryClass::Object.to_value()),
916 (Attribute::Class, EntryClass::Person.to_value()),
917 (Attribute::Class, EntryClass::Account.to_value()),
918 (Attribute::Name, Value::new_iname("testperson1")),
919 (Attribute::Uuid, Value::Uuid(user_uuid)),
920 (Attribute::Description, Value::new_utf8s("testperson1")),
921 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
922 );
923 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
924
925 assert!(server_txn.internal_create(vec![e1]).is_ok());
926
927 let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
930
931 let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
932
933 let me_inv_m = ModifyEvent::new_impersonate_identity(
935 user_ident,
936 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
937 ModifyList::new_list(vec![
938 Modify::Purged(Attribute::Name),
939 Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
940 Modify::Purged(Attribute::DisplayName),
941 Modify::Present(
942 Attribute::DisplayName,
943 Value::Utf8("test_person_renamed".into()),
944 ),
945 Modify::Purged(Attribute::LegalName),
946 Modify::Present(
947 Attribute::LegalName,
948 Value::Utf8("test_person_renamed".into()),
949 ),
950 ]),
951 );
952
953 assert!(server_txn.modify(&me_inv_m).is_ok());
955
956 let modify_remove_person = ModifyEvent::new_internal_invalid(
958 filter!(f_eq(
959 Attribute::Uuid,
960 PartialValue::Uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
961 )),
962 ModifyList::new_list(vec![Modify::Purged(Attribute::Member)]),
963 );
964
965 assert!(server_txn.modify(&modify_remove_person).is_ok());
966
967 let testperson_entry = server_txn.internal_search_uuid(user_uuid).unwrap();
969
970 let user_ident = Identity::from_impersonate_entry_readwrite(testperson_entry);
971
972 let me_inv_m = ModifyEvent::new_impersonate_identity(
973 user_ident,
974 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(user_uuid),)),
975 ModifyList::new_list(vec![
976 Modify::Purged(Attribute::Name),
977 Modify::Present(Attribute::Name, Value::new_iname("test_person_renamed")),
978 Modify::Purged(Attribute::DisplayName),
979 Modify::Present(
980 Attribute::DisplayName,
981 Value::Utf8("test_person_renamed".into()),
982 ),
983 Modify::Purged(Attribute::LegalName),
984 Modify::Present(
985 Attribute::LegalName,
986 Value::Utf8("test_person_renamed".into()),
987 ),
988 ]),
989 );
990
991 assert_eq!(
993 server_txn.modify(&me_inv_m),
994 Err(OperationError::AccessDenied)
995 );
996 }
997}