1use crate::prelude::*;
2use crate::server::batch_modify::ModSetValid;
3use crypto_glue::s256::Sha256Output;
4use std::collections::{BTreeMap, BTreeSet};
5
6pub enum AttributeAssertion {
7 Set(ValueSet),
9 Absent,
11 }
13
14impl From<ValueSet> for AttributeAssertion {
15 fn from(vs: ValueSet) -> Self {
16 AttributeAssertion::Set(vs)
17 }
18}
19
20pub enum EntryAssertion {
21 Present {
24 target: Uuid,
25 attrs: BTreeMap<Attribute, Option<ValueSet>>,
27 },
28 Absent {
29 target: Uuid,
30 },
31}
32
33#[derive(Default)]
34pub enum AssertOnce {
35 #[default]
36 No,
37 Yes {
38 id: Uuid,
39 nonce: Sha256Output,
40 },
41}
42
43pub struct AssertEvent {
44 pub ident: Identity,
45 pub asserts: Vec<EntryAssertion>,
46 pub once: AssertOnce,
47}
48
49struct Assertion {
50 target: Uuid,
51 attrs: BTreeMap<Attribute, Option<ValueSet>>,
52}
53
54enum AssertionInner {
55 None,
56 Create { asserts: Vec<Assertion> },
57 Modify { asserts: Vec<Assertion> },
58 Remove { targets: Vec<Uuid> },
59}
60
61impl QueryServerWriteTransaction<'_> {
62 #[instrument(level = "debug", skip_all)]
63 pub fn assert(&mut self, ae: AssertEvent) -> Result<(), OperationError> {
65 let AssertEvent {
66 ident,
67 asserts,
68 once,
69 } = ae;
70
71 if let AssertOnce::Yes { id, nonce } = once {
72 let filter = filter!(f_and(vec![
76 f_eq(Attribute::Uuid, PartialValue::Uuid(id)),
77 f_eq(Attribute::Class, EntryClass::AssertionNonce.into())
78 ]));
79
80 let search_result = self.internal_search(filter).or_else(|err| {
81 if err == OperationError::NoMatchingEntries {
82 Ok(Vec::with_capacity(0))
83 } else {
84 Err(err)
85 }
86 })?;
87
88 if let Some(record) = search_result.first() {
89 if record
90 .get_ava_as_s256_set(Attribute::S256)
91 .map(|set| set.contains(&nonce))
92 .unwrap_or_default()
93 {
94 info!(?id, "Assertion already applied, skipping.");
96 return Ok(());
97 } else {
98 let ml = ModifyList::new_list(vec![Modify::Set(
100 Attribute::S256,
101 ValueSetSha256::new(nonce) as ValueSet,
102 )]);
103
104 self.internal_batch_modify([(id, ml)].into_iter())?;
105 }
106 } else {
107 let entry = EntryInitNew::from_iter([
110 (
111 Attribute::Class,
112 vs_iutf8!(EntryClass::AssertionNonce.into()),
113 ),
114 (Attribute::Uuid, ValueSetUuid::new(id) as ValueSet),
115 (Attribute::S256, ValueSetSha256::new(nonce) as ValueSet),
116 ]);
117
118 self.internal_create(vec![entry]).inspect_err(|err| {
119 error!(?err, "Failed to creation assertion nonce.");
120 })?;
121 }
122
123 };
125
126 if asserts.is_empty() {
128 error!("assert: empty request");
129 return Err(OperationError::EmptyRequest);
130 }
131
132 let mut duplicates: BTreeSet<_> = Default::default();
136 let mut present_uuids: BTreeSet<Uuid> = Default::default();
137 let mut absent_uuids: BTreeSet<Uuid> = Default::default();
138
139 for assert in &asserts {
140 match assert {
141 EntryAssertion::Present { target, .. } => {
142 if !present_uuids.insert(*target) {
144 duplicates.insert(*target);
145 }
146 }
147 EntryAssertion::Absent { target } => {
148 if !absent_uuids.insert(*target) {
149 duplicates.insert(*target);
150 }
151 }
152 }
153 }
154
155 duplicates.extend(present_uuids.intersection(&absent_uuids));
157
158 if !duplicates.is_empty() {
161 error!(?duplicates, "entry uuids in SCIM Assertion must be unique.");
163 return Err(OperationError::SC0033AssertionContainsDuplicateUuids);
164 }
165
166 let filter = filter!(f_or(
169 present_uuids
170 .iter()
171 .copied()
172 .chain(absent_uuids.iter().copied())
173 .map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
174 .collect()
175 ));
176
177 let existing_entries = self.internal_search(filter).or_else(|err| {
180 if err == OperationError::NoMatchingEntries {
181 Ok(Vec::with_capacity(0))
182 } else {
183 Err(err)
184 }
185 })?;
186
187 let existing_uuids: BTreeSet<Uuid> = existing_entries
189 .iter()
190 .map(|entry| entry.get_uuid())
191 .collect();
192
193 let create_uuids: BTreeSet<Uuid> =
194 present_uuids.difference(&existing_uuids).copied().collect();
195
196 let delete_uuids: BTreeSet<Uuid> = absent_uuids
198 .intersection(&existing_uuids)
199 .copied()
200 .collect();
201
202 let mut working_assert = AssertionInner::None;
212
213 let mut assert_batches = Vec::with_capacity(asserts.len());
214
215 for entry_assert in asserts.into_iter() {
216 match entry_assert {
217 EntryAssertion::Absent { target } => {
218 if !delete_uuids.contains(&target) {
219 continue;
222 }
223
224 if let AssertionInner::Remove { targets } = &mut working_assert {
225 targets.push(target)
227 } else {
228 let mut new_assert = AssertionInner::Remove {
229 targets: vec![target],
230 };
231
232 std::mem::swap(&mut new_assert, &mut working_assert);
233
234 assert_batches.push(new_assert);
235 }
236 }
237
238 EntryAssertion::Present { target, attrs } if create_uuids.contains(&target) => {
239 if let AssertionInner::Create { asserts } = &mut working_assert {
240 asserts.push(Assertion { target, attrs })
242 } else {
243 let mut new_assert = AssertionInner::Create {
244 asserts: vec![Assertion { target, attrs }],
245 };
246
247 std::mem::swap(&mut new_assert, &mut working_assert);
248
249 assert_batches.push(new_assert);
250 }
251 }
252
253 EntryAssertion::Present { target, attrs } => {
254 if let AssertionInner::Modify { asserts } = &mut working_assert {
255 asserts.push(Assertion { target, attrs })
257 } else {
258 let mut new_assert = AssertionInner::Modify {
259 asserts: vec![Assertion { target, attrs }],
260 };
261
262 std::mem::swap(&mut new_assert, &mut working_assert);
263
264 assert_batches.push(new_assert);
265 }
266 }
267 }
268 }
269
270 assert_batches.push(working_assert);
272
273 for assertion in assert_batches.into_iter() {
276 match assertion {
277 AssertionInner::Create { asserts } => {
278 let entries = asserts
279 .into_iter()
280 .map(|Assertion { target, attrs }| {
281 let mut attrs: crate::entry::Eattrs = attrs
283 .into_iter()
284 .filter_map(|(attr, assert_valueset)| {
285 assert_valueset.map(|vs| (attr, vs))
288 })
289 .collect();
290
291 attrs.insert(Attribute::Uuid, ValueSetUuid::new(target));
292
293 EntryInitNew::from_iter(attrs.into_iter())
294 })
295 .collect();
296
297 let create_event = CreateEvent {
298 ident: ident.clone(),
299 entries,
300 return_created_uuids: false,
301 };
302
303 self.create(&create_event)?;
304 }
305 AssertionInner::Modify { asserts } => {
306 let modset = asserts
307 .into_iter()
308 .map(|Assertion { target, attrs }| {
309 let ml = attrs
310 .into_iter()
311 .map(|(attr, assert)| match assert {
312 Some(vs) => Modify::Set(attr, vs),
313 None => Modify::Purged(attr),
314 })
315 .collect();
316
317 let ml = ModifyList::new_list(ml);
318
319 (target, ml)
320 })
321 .map(|(target, ml)| {
322 ml.validate(self.get_schema())
323 .map(|modlist| (target, modlist))
324 .map_err(OperationError::SchemaViolation)
325 })
326 .collect::<Result<ModSetValid, _>>()?;
327
328 let batch_modify_event = BatchModifyEvent {
329 ident: ident.clone(),
330 modset,
331 };
332
333 self.batch_modify(&batch_modify_event)?;
334 }
335 AssertionInner::Remove { targets } => {
336 let filter = Filter::new(f_or(
337 targets
338 .into_iter()
339 .map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
340 .collect(),
341 ));
342
343 let filter_orig = filter
344 .validate(self.get_schema())
345 .map_err(OperationError::SchemaViolation)?;
346 let filter = filter_orig.clone().into_ignore_hidden();
347
348 let delete_event = DeleteEvent {
349 ident: ident.clone(),
350 filter,
351 filter_orig,
352 };
353
354 self.delete(&delete_event)?;
355 }
356 AssertionInner::None => {}
357 }
358 }
359
360 Ok(())
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::{AssertEvent, AssertOnce, EntryAssertion};
368 use crate::prelude::*;
369 use crypto_glue::s256::Sha256;
370 use crypto_glue::traits::*;
371 use std::collections::BTreeMap;
372 #[qs_test]
375 async fn test_entry_asserts_basic(server: &QueryServer) {
376 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
377
378 let assert_event = AssertEvent {
379 ident: Identity::from_internal(),
380 asserts: vec![],
381 once: AssertOnce::No,
382 };
383
384 let err = server_txn.assert(assert_event).expect_err("Should Fail!");
385 assert_eq!(err, OperationError::EmptyRequest);
386
387 let uuid_a = Uuid::new_v4();
391
392 let assert_event = AssertEvent {
393 ident: Identity::from_internal(),
394 asserts: vec![
395 EntryAssertion::Absent { target: uuid_a },
396 EntryAssertion::Absent { target: uuid_a },
397 ],
398 once: AssertOnce::No,
399 };
400
401 let err = server_txn.assert(assert_event).expect_err("Should Fail!");
402 assert_eq!(err, OperationError::SC0033AssertionContainsDuplicateUuids);
403
404 let assert_event = AssertEvent {
406 ident: Identity::from_internal(),
407 asserts: vec![
408 EntryAssertion::Absent { target: uuid_a },
409 EntryAssertion::Present {
410 target: uuid_a,
411 attrs: BTreeMap::default(),
412 },
413 ],
414 once: AssertOnce::No,
415 };
416
417 let err = server_txn.assert(assert_event).expect_err("Should Fail!");
418 assert_eq!(err, OperationError::SC0033AssertionContainsDuplicateUuids);
419
420 let assert_event = AssertEvent {
422 ident: Identity::from_internal(),
423 asserts: vec![
424 EntryAssertion::Present {
425 target: uuid_a,
426 attrs: BTreeMap::default(),
427 },
428 EntryAssertion::Present {
429 target: uuid_a,
430 attrs: BTreeMap::default(),
431 },
432 ],
433 once: AssertOnce::No,
434 };
435
436 let err = server_txn.assert(assert_event).expect_err("Should Fail!");
437 assert_eq!(err, OperationError::SC0033AssertionContainsDuplicateUuids);
438
439 let assert_event = AssertEvent {
442 ident: Identity::from_internal(),
443 asserts: vec![EntryAssertion::Present {
444 target: uuid_a,
445 attrs: BTreeMap::from([
446 (
447 Attribute::Class,
448 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
449 ),
450 (Attribute::Name, vs_iname!("test_entry_a").into()),
451 (
452 Attribute::DisplayName,
453 vs_utf8!("Test Entry A".into()).into(),
454 ),
455 ]),
456 }],
457 once: AssertOnce::No,
458 };
459
460 server_txn.assert(assert_event).expect("Must Succeed");
461
462 let entry_a = server_txn
463 .internal_search_uuid(uuid_a)
464 .expect("Must succeed");
465 assert_eq!(
466 entry_a.get_ava_single_utf8(Attribute::DisplayName),
467 Some("Test Entry A")
468 );
469
470 let assert_event = AssertEvent {
473 ident: Identity::from_internal(),
474 asserts: vec![EntryAssertion::Present {
475 target: uuid_a,
476 attrs: BTreeMap::from([(
477 Attribute::DisplayName,
478 vs_utf8!("Test Entry A Updated".into()).into(),
479 )]),
480 }],
481 once: AssertOnce::No,
482 };
483
484 server_txn.assert(assert_event).expect("Must Succeed");
485
486 let entry_a = server_txn
487 .internal_search_uuid(uuid_a)
488 .expect("Must succeed");
489 assert_eq!(
490 entry_a.get_ava_single_utf8(Attribute::DisplayName),
491 Some("Test Entry A Updated")
492 );
493
494 let assert_event = AssertEvent {
497 ident: Identity::from_internal(),
498 asserts: vec![EntryAssertion::Absent { target: uuid_a }],
499 once: AssertOnce::No,
500 };
501
502 server_txn.assert(assert_event).expect("Must Succeed");
503
504 let err = server_txn
505 .internal_search_uuid(uuid_a)
506 .expect_err("Must fail");
507
508 assert_eq!(err, OperationError::NoMatchingEntries);
510
511 let uuid_b = Uuid::new_v4();
515 let uuid_c = Uuid::new_v4();
516 let uuid_d = Uuid::new_v4();
517
518 let assert_event = AssertEvent {
520 ident: Identity::from_internal(),
521 asserts: vec![
522 EntryAssertion::Present {
523 target: uuid_b,
524 attrs: BTreeMap::from([
525 (
526 Attribute::Class,
527 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
528 ),
529 (Attribute::Name, vs_iname!("test_entry_b").into()),
530 (
531 Attribute::DisplayName,
532 vs_utf8!("Test Entry B".into()).into(),
533 ),
534 ]),
535 },
536 EntryAssertion::Present {
537 target: uuid_d,
538 attrs: BTreeMap::from([
539 (
540 Attribute::Class,
541 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
542 ),
543 (Attribute::Name, vs_iname!("test_entry_d").into()),
544 (
545 Attribute::DisplayName,
546 vs_utf8!("Test Entry D".into()).into(),
547 ),
548 ]),
549 },
550 ],
551 once: AssertOnce::No,
552 };
553
554 server_txn.assert(assert_event).expect("Must Succeed");
555 assert!(server_txn
556 .internal_exists_uuid(uuid_b)
557 .expect("Failed to check existance"));
558 assert!(server_txn
559 .internal_exists_uuid(uuid_d)
560 .expect("Failed to check existance"));
561
562 let assert_event = AssertEvent {
565 ident: Identity::from_internal(),
566 asserts: vec![
567 EntryAssertion::Present {
568 target: uuid_b,
569 attrs: BTreeMap::from([
570 (
571 Attribute::Class,
572 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
573 ),
574 (Attribute::Name, vs_iname!("test_entry_b").into()),
575 (
576 Attribute::DisplayName,
577 vs_utf8!("Test Entry B".into()).into(),
578 ),
579 ]),
580 },
581 EntryAssertion::Present {
582 target: uuid_c,
583 attrs: BTreeMap::from([
584 (
585 Attribute::Class,
586 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
587 ),
588 (Attribute::Name, vs_iname!("test_entry_c").into()),
589 (
590 Attribute::DisplayName,
591 vs_utf8!("Test Entry C".into()).into(),
592 ),
593 ]),
594 },
595 EntryAssertion::Present {
596 target: uuid_d,
597 attrs: BTreeMap::from([
598 (
599 Attribute::Class,
600 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
601 ),
602 (Attribute::Name, vs_iname!("test_entry_d").into()),
603 (
604 Attribute::DisplayName,
605 vs_utf8!("Test Entry D".into()).into(),
606 ),
607 ]),
608 },
609 ],
610 once: AssertOnce::No,
611 };
612
613 server_txn.assert(assert_event).expect("Must Succeed");
614 assert!(server_txn
615 .internal_exists_uuid(uuid_b)
616 .expect("Failed to check existance"));
617 assert!(server_txn
618 .internal_exists_uuid(uuid_c)
619 .expect("Failed to check existance"));
620 assert!(server_txn
621 .internal_exists_uuid(uuid_d)
622 .expect("Failed to check existance"));
623
624 let assert_event = AssertEvent {
627 ident: Identity::from_internal(),
628 asserts: vec![
629 EntryAssertion::Absent { target: uuid_b },
630 EntryAssertion::Present {
631 target: uuid_c,
632 attrs: BTreeMap::from([
633 (
634 Attribute::Class,
635 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
636 ),
637 (Attribute::Name, vs_iname!("test_entry_c").into()),
638 (
639 Attribute::DisplayName,
640 vs_utf8!("Test Entry C".into()).into(),
641 ),
642 ]),
643 },
644 EntryAssertion::Absent { target: uuid_d },
645 ],
646 once: AssertOnce::No,
647 };
648
649 server_txn.assert(assert_event).expect("Must Succeed");
650
651 assert!(!server_txn
652 .internal_exists_uuid(uuid_b)
653 .expect("Failed to check existance"));
654 assert!(server_txn
655 .internal_exists_uuid(uuid_c)
656 .expect("Failed to check existance"));
657 assert!(!server_txn
658 .internal_exists_uuid(uuid_d)
659 .expect("Failed to check existance"));
660 }
661
662 #[qs_test]
663 async fn test_entry_asserts_nonce(server: &QueryServer) {
664 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
669
670 let uuid_a = Uuid::new_v4();
671 let assert_id = Uuid::new_v4();
672 let nonce_1 = {
673 let mut hasher = Sha256::new();
674 hasher.update([1]);
675 hasher.finalize()
676 };
677
678 let nonce_2 = {
679 let mut hasher = Sha256::new();
680 hasher.update([2]);
681 hasher.finalize()
682 };
683
684 let assert_event = AssertEvent {
685 ident: Identity::from_internal(),
686 asserts: vec![EntryAssertion::Present {
687 target: uuid_a,
688 attrs: BTreeMap::from([
689 (
690 Attribute::Class,
691 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
692 ),
693 (Attribute::Name, vs_iname!("test_entry_a").into()),
694 (
695 Attribute::DisplayName,
696 vs_utf8!("Test Entry A".into()).into(),
697 ),
698 ]),
699 }],
700 once: AssertOnce::Yes {
701 id: assert_id,
702 nonce: nonce_1,
703 },
704 };
705
706 server_txn.assert(assert_event).expect("Must Succeed");
707
708 let entry_a = server_txn
709 .internal_search_uuid(uuid_a)
710 .expect("Must succeed");
711 assert_eq!(
712 entry_a.get_ava_single_utf8(Attribute::DisplayName),
713 Some("Test Entry A")
714 );
715
716 let assert_event = AssertEvent {
718 ident: Identity::from_internal(),
719 asserts: vec![EntryAssertion::Present {
720 target: uuid_a,
721 attrs: BTreeMap::from([
722 (
723 Attribute::Class,
724 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
725 ),
726 (Attribute::Name, vs_iname!("test_entry_a").into()),
727 (
728 Attribute::DisplayName,
729 vs_utf8!("Test Entry A Updated".into()).into(),
732 ),
733 ]),
734 }],
735 once: AssertOnce::Yes {
736 id: assert_id,
737 nonce: nonce_1,
739 },
740 };
741
742 server_txn.assert(assert_event).expect("Must Succeed");
743
744 let entry_a = server_txn
745 .internal_search_uuid(uuid_a)
746 .expect("Must succeed");
747 assert_eq!(
748 entry_a.get_ava_single_utf8(Attribute::DisplayName),
749 Some("Test Entry A")
750 );
751
752 let assert_event = AssertEvent {
755 ident: Identity::from_internal(),
756 asserts: vec![EntryAssertion::Present {
757 target: uuid_a,
758 attrs: BTreeMap::from([
759 (
760 Attribute::Class,
761 vs_iutf8!(EntryClass::Person.into(), EntryClass::Account.into()).into(),
762 ),
763 (Attribute::Name, vs_iname!("test_entry_a").into()),
764 (
765 Attribute::DisplayName,
766 vs_utf8!("Test Entry A Updated".into()).into(),
769 ),
770 ]),
771 }],
772 once: AssertOnce::Yes {
773 id: assert_id,
774 nonce: nonce_2,
776 },
777 };
778
779 server_txn.assert(assert_event).expect("Must Succeed");
780
781 let entry_a = server_txn
782 .internal_search_uuid(uuid_a)
783 .expect("Must succeed");
784 assert_eq!(
785 entry_a.get_ava_single_utf8(Attribute::DisplayName),
786 Some("Test Entry A Updated")
787 );
788 }
789}