1use std::collections::BTreeSet;
2use std::iter::once;
3use std::sync::Arc;
4
5use hashbrown::HashSet;
6
7use crate::event::{CreateEvent, ModifyEvent};
8use crate::modify::Modify;
9use crate::plugins::Plugin;
10use crate::prelude::*;
11
12pub struct Base {}
23
24impl Plugin for Base {
25 fn id() -> &'static str {
26 "plugin_base"
27 }
28
29 #[instrument(level = "debug", name = "base_pre_create_transform", skip_all)]
30 #[allow(clippy::cognitive_complexity)]
31 fn pre_create_transform(
32 qs: &mut QueryServerWriteTransaction,
33 cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
34 ce: &CreateEvent,
35 ) -> Result<(), OperationError> {
36 for entry in cand.iter_mut() {
39 entry.add_ava(Attribute::Class, EntryClass::Object.to_value());
41
42 match entry.get_ava_set(Attribute::Uuid).map(|s| s.len()) {
44 None => {
45 let ava_uuid = Value::Uuid(Uuid::new_v4());
47 trace!("Setting temporary UUID {:?} to entry", ava_uuid);
48 entry.set_ava(&Attribute::Uuid, once(ava_uuid));
49 }
50 Some(1) => {
51 }
53 Some(x) => {
54 admin_error!(
56 "Entry defines {} attr, but has multiple ({}) values.",
57 Attribute::Uuid,
58 x
59 );
60 return Err(OperationError::Plugin(PluginError::Base(
61 "Uuid has multiple values".to_string(),
62 )));
63 }
64 };
65 }
66
67 let mut cand_uuid: BTreeSet<Uuid> = BTreeSet::new();
69
70 let mut system_range_invalid = false;
71
72 for entry in cand.iter_mut() {
78 let uuid_ref: Uuid = entry
79 .get_ava_single_uuid(Attribute::Uuid)
80 .ok_or_else(|| OperationError::InvalidAttribute(Attribute::Uuid.to_string()))?;
81
82 if uuid_ref < DYNAMIC_RANGE_MINIMUM_UUID {
85 if ce.ident.is_internal() {
86 entry.add_ava(Attribute::Class, EntryClass::Builtin.to_value());
88 } else {
89 error!(
91 "uuid from protected system UUID range found in create set! {:?}",
92 uuid_ref
93 );
94 system_range_invalid = true;
95 }
96 };
97
98 if !cand_uuid.insert(uuid_ref) {
99 trace!("uuid duplicate found in create set! {:?}", uuid_ref);
100 return Err(OperationError::Plugin(PluginError::Base(
101 "Uuid duplicate detected in request".to_string(),
102 )));
103 }
104 }
105
106 if system_range_invalid {
107 return Err(OperationError::Plugin(PluginError::Base(
108 "Uuid must not be in protected range".to_string(),
109 )));
110 }
111
112 if cand_uuid.contains(&UUID_DOES_NOT_EXIST) {
113 error!(
114 "uuid \"does not exist\" found in create set! THIS IS A BUG. PLEASE REPORT IT IMMEDIATELY."
115 );
116 return Err(OperationError::Plugin(PluginError::Base(
117 "Attempt to create UUID_DOES_NOT_EXIST".to_string(),
118 )));
119 }
120
121 let filt_in = filter_all!(FC::Or(
125 cand_uuid
126 .into_iter()
127 .map(|u| FC::Eq(Attribute::Uuid, PartialValue::Uuid(u)))
128 .collect(),
129 ));
130
131 let r = qs.internal_exists(filt_in);
139
140 match r {
141 Ok(b) => {
142 if b {
143 admin_error!("A UUID already exists, rejecting.");
144 return Err(OperationError::Plugin(PluginError::Base(
145 "Uuid duplicate found in database".to_string(),
146 )));
147 }
148 }
149 Err(e) => {
150 admin_error!("Error occurred checking UUID existence. {:?}", e);
151 return Err(e);
152 }
153 }
154
155 Ok(())
156 }
157
158 #[instrument(level = "debug", name = "base_pre_modify", skip_all)]
159 fn pre_modify(
160 _qs: &mut QueryServerWriteTransaction,
161 _pre_cand: &[Arc<EntrySealedCommitted>],
162 _cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
163 me: &ModifyEvent,
164 ) -> Result<(), OperationError> {
165 me.modlist.iter().try_for_each(|modify| {
166 let attr = match &modify {
167 Modify::Present(a, _)
168 | Modify::Removed(a, _)
169 | Modify::Purged(a)
170 | Modify::Set(a, _) => Some(a),
171 Modify::Assert(_, _) => None,
172 };
173 if attr == Some(&Attribute::Uuid) {
174 debug!(?modify, "Modify in violation");
175 request_error!("Modifications to UUID's are NOT ALLOWED");
176 Err(OperationError::SystemProtectedAttribute)
177 } else {
178 Ok(())
179 }
180 })
181 }
182
183 #[instrument(level = "debug", name = "base_pre_modify", skip_all)]
184 fn pre_batch_modify(
185 _qs: &mut QueryServerWriteTransaction,
186 _pre_cand: &[Arc<EntrySealedCommitted>],
187 _cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
188 me: &BatchModifyEvent,
189 ) -> Result<(), OperationError> {
190 me.modset
191 .values()
192 .flat_map(|ml| ml.iter())
193 .try_for_each(|modify| {
194 let attr = match &modify {
195 Modify::Present(a, _)
196 | Modify::Removed(a, _)
197 | Modify::Set(a, _)
198 | Modify::Purged(a) => Some(a),
199 Modify::Assert(_, _) => None,
200 };
201 if attr == Some(&Attribute::Uuid) {
202 debug!(?modify, "Modify in violation");
203 request_error!("Modifications to UUID's are NOT ALLOWED");
204 Err(OperationError::SystemProtectedAttribute)
205 } else {
206 Ok(())
207 }
208 })
209 }
210
211 #[instrument(level = "debug", name = "base::verify", skip_all)]
212 fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
213 let entries = match qs.internal_search(filter!(f_pres(Attribute::Class))) {
215 Ok(v) => v,
216 Err(e) => {
217 admin_error!("Internal Search Failure: {:?}", e);
218 return vec![Err(ConsistencyError::QueryServerSearchFailure)];
219 }
220 };
221
222 let mut uuid_seen: HashSet<Uuid> = HashSet::with_capacity(entries.len());
223
224 entries
225 .iter()
226 .map(|e| {
228 let uuid = e.get_uuid();
233
234 if uuid_seen.insert(uuid) {
235 Ok(())
237 } else {
238 Err(ConsistencyError::UuidNotUnique(uuid.to_string()))
239 }
240 })
241 .filter(|v| v.is_err())
242 .collect()
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use crate::prelude::*;
249 use std::sync::Arc;
250
251 const UUID_TEST_ACCOUNT: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
252 const UUID_TEST_GROUP: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
253 const UUID_TEST_ACP: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
254
255 lazy_static! {
256 pub static ref TEST_ACCOUNT: EntryInitNew = entry_init!(
257 (Attribute::Class, EntryClass::Account.to_value()),
258 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
259 (Attribute::Class, EntryClass::MemberOf.to_value()),
260 (Attribute::Name, Value::new_iname("test_account_1")),
261 (Attribute::DisplayName, Value::new_utf8s("test_account_1")),
262 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT)),
263 (Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP))
264 );
265 pub static ref TEST_GROUP: EntryInitNew = entry_init!(
266 (Attribute::Class, EntryClass::Group.to_value()),
267 (Attribute::Name, Value::new_iname("test_group_a")),
268 (Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP)),
269 (Attribute::Member, Value::Refer(UUID_TEST_ACCOUNT))
270 );
271 pub static ref ALLOW_ALL: EntryInitNew = entry_init!(
272 (Attribute::Class, EntryClass::Object.to_value()),
273 (
274 Attribute::Class,
275 EntryClass::AccessControlProfile.to_value()
276 ),
277 (
278 Attribute::Class,
279 EntryClass::AccessControlTargetScope.to_value()
280 ),
281 (
282 Attribute::Class,
283 EntryClass::AccessControlReceiverGroup.to_value()
284 ),
285 (Attribute::Class, EntryClass::AccessControlModify.to_value()),
286 (Attribute::Class, EntryClass::AccessControlCreate.to_value()),
287 (Attribute::Class, EntryClass::AccessControlDelete.to_value()),
288 (Attribute::Class, EntryClass::AccessControlSearch.to_value()),
289 (
290 Attribute::Name,
291 Value::new_iname("idm_admins_acp_allow_all_test")
292 ),
293 (Attribute::Uuid, Value::Uuid(UUID_TEST_ACP)),
294 (Attribute::AcpReceiverGroup, Value::Refer(UUID_TEST_GROUP)),
295 (
296 Attribute::AcpTargetScope,
297 Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
298 ),
299 (Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
300 (Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
301 (Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
302 (Attribute::AcpModifyClass, EntryClass::System.to_value()),
303 (
304 Attribute::AcpModifyRemovedAttr,
305 Value::from(Attribute::Class)
306 ),
307 (
308 Attribute::AcpModifyRemovedAttr,
309 Value::from(Attribute::DisplayName)
310 ),
311 (Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
312 (
313 Attribute::AcpModifyRemovedAttr,
314 Value::from(Attribute::Must)
315 ),
316 (
317 Attribute::AcpModifyPresentAttr,
318 Value::from(Attribute::Class)
319 ),
320 (
321 Attribute::AcpModifyPresentAttr,
322 Value::from(Attribute::DisplayName)
323 ),
324 (Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
325 (
326 Attribute::AcpModifyPresentAttr,
327 Value::from(Attribute::Must)
328 ),
329 (Attribute::AcpCreateClass, EntryClass::Object.to_value()),
330 (Attribute::AcpCreateClass, EntryClass::Person.to_value()),
331 (Attribute::AcpCreateClass, EntryClass::System.to_value()),
332 (Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
333 (Attribute::AcpCreateAttr, Value::from(Attribute::Class)),
334 (
335 Attribute::AcpCreateAttr,
336 Value::from(Attribute::Description)
337 ),
338 (
339 Attribute::AcpCreateAttr,
340 Value::from(Attribute::DisplayName)
341 ),
342 (Attribute::AcpCreateAttr, Value::from(Attribute::Uuid))
343 );
344 pub static ref PRELOAD: Vec<EntryInitNew> =
345 vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
346 pub static ref E_TEST_ACCOUNT: Arc<EntrySealedCommitted> =
347 Arc::new(TEST_ACCOUNT.clone().into_sealed_committed());
348 }
349
350 #[test]
352 fn test_pre_create_no_uuid() {
353 let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
354
355 let e = entry_init!(
356 (Attribute::Class, EntryClass::Person.to_value()),
357 (Attribute::Class, EntryClass::Account.to_value()),
358 (Attribute::Name, Value::new_iname("testperson")),
359 (
360 Attribute::DisplayName,
361 Value::Utf8("Test Person".to_string())
362 )
363 );
364
365 let create = vec![e];
366
367 run_create_test!(
368 Ok(()),
369 preload,
370 create,
371 None,
372 |qs: &mut QueryServerWriteTransaction| {
373 let cands = qs
374 .internal_search(filter!(f_eq(
375 Attribute::Name,
376 PartialValue::new_iname("testperson")
377 )))
378 .expect("Internal search failure");
379 let ue = cands.first().expect("No cand");
380 assert!(ue.attribute_pres(Attribute::Uuid));
381 }
382 );
383 }
384
385 #[test]
387 fn test_pre_create_uuid_invalid() {
388 let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
389
390 let e = entry_init!(
391 (Attribute::Class, EntryClass::Person.to_value()),
392 (Attribute::Class, EntryClass::Account.to_value()),
393 (Attribute::Name, Value::new_iname("testperson")),
394 (
395 Attribute::DisplayName,
396 Value::Utf8("Test Person".to_string())
397 ),
398 (Attribute::Uuid, Value::Utf8("xxxxxx".to_string()))
399 );
400
401 let create = vec![e];
402
403 run_create_test!(
404 Err(OperationError::InvalidAttribute(
405 Attribute::Uuid.to_string()
406 )),
407 preload,
408 create,
409 None,
410 |_| {}
411 );
412 }
413
414 #[test]
416 fn test_pre_create_uuid_empty() {
417 let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
418
419 let mut e = entry_init!(
420 (Attribute::Class, EntryClass::Person.to_value()),
421 (Attribute::Class, EntryClass::Account.to_value()),
422 (Attribute::Name, Value::new_iname("testperson")),
423 (
424 Attribute::DisplayName,
425 Value::Utf8("Test Person".to_string())
426 ),
427 (
428 Attribute::Uuid,
429 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
430 )
431 );
432
433 let vs = e.get_ava_mut(Attribute::Uuid).unwrap();
434 vs.clear();
435
436 let create = vec![e.clone()];
437
438 run_create_test!(
439 Err(OperationError::Plugin(PluginError::Base(
440 "Uuid format invalid".to_string()
441 ))),
442 preload,
443 create,
444 None,
445 |_| {}
446 );
447 }
448
449 #[test]
451 fn test_pre_create_uuid_valid() {
452 let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
453
454 let e = entry_init!(
455 (Attribute::Class, EntryClass::Person.to_value()),
456 (Attribute::Class, EntryClass::Account.to_value()),
457 (Attribute::Name, Value::new_iname("testperson")),
458 (
459 Attribute::DisplayName,
460 Value::Utf8("Test Person".to_string())
461 ),
462 (
463 Attribute::Uuid,
464 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
465 )
466 );
467
468 let create = vec![e];
469
470 run_create_test!(
471 Ok(()),
472 preload,
473 create,
474 None,
475 |qs: &mut QueryServerWriteTransaction| {
476 let cands = qs
477 .internal_search(filter!(f_eq(
478 Attribute::Name,
479 PartialValue::new_iname("testperson")
480 )))
481 .expect("Internal search failure");
482 let ue = cands.first().expect("No cand");
483 assert!(ue.attribute_equality(
484 Attribute::Uuid,
485 &PartialValue::Uuid(uuid!("79724141-3603-4060-b6bb-35c72772611d"))
486 ));
487 }
488 );
489 }
490
491 #[test]
492 fn test_pre_create_uuid_valid_multi() {
493 let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
494
495 let e = entry_init!(
496 (Attribute::Class, EntryClass::Person.to_value()),
497 (Attribute::Class, EntryClass::Account.to_value()),
498 (Attribute::Name, Value::new_iname("testperson")),
499 (
500 Attribute::DisplayName,
501 Value::Utf8("Test Person".to_string())
502 ),
503 (
504 Attribute::Uuid,
505 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611e"))
506 ),
507 (
508 Attribute::Uuid,
509 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
510 )
511 );
512
513 let create = vec![e];
514
515 run_create_test!(
516 Err(OperationError::Plugin(PluginError::Base(
517 "Uuid has multiple values".to_string()
518 ))),
519 preload,
520 create,
521 None,
522 |_| {}
523 );
524 }
525
526 #[test]
535 fn test_pre_create_uuid_exist() {
536 let e = entry_init!(
537 (Attribute::Class, EntryClass::Person.to_value()),
538 (Attribute::Class, EntryClass::Account.to_value()),
539 (Attribute::Name, Value::new_iname("testperson")),
540 (
541 Attribute::DisplayName,
542 Value::Utf8("Test Person".to_string())
543 ),
544 (
545 Attribute::Uuid,
546 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
547 )
548 );
549
550 let create = vec![e.clone()];
551 let preload = vec![e];
552
553 run_create_test!(
554 Err(OperationError::Plugin(PluginError::Base(
555 "Uuid duplicate found in database".to_string()
556 ))),
557 preload,
558 create,
559 None,
560 |_| {}
561 );
562 }
563
564 #[test]
565 fn test_pre_create_double_uuid() {
566 let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
568
569 let ea = entry_init!(
570 (Attribute::Class, EntryClass::Person.to_value()),
571 (Attribute::Class, EntryClass::Account.to_value()),
572 (Attribute::Name, Value::new_iname("testperson")),
573 (
574 Attribute::DisplayName,
575 Value::Utf8("Test Person".to_string())
576 ),
577 (
578 Attribute::Uuid,
579 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
580 )
581 );
582
583 let eb = ea.clone();
584
585 let create = vec![ea, eb];
586
587 run_create_test!(
588 Err(OperationError::Plugin(PluginError::Base(
589 "Uuid duplicate detected in request".to_string()
590 ))),
591 preload,
592 create,
593 None,
594 |_| {}
595 );
596 }
597
598 #[test]
600 fn test_modify_uuid_present() {
601 let ea = entry_init!(
603 (Attribute::Class, EntryClass::Group.to_value()),
604 (Attribute::Name, Value::new_iname("testgroup_a")),
605 (
606 Attribute::Uuid,
607 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
608 )
609 );
610
611 let preload = vec![ea];
612
613 run_modify_test!(
614 Err(OperationError::SystemProtectedAttribute),
615 preload,
616 filter!(f_eq(
617 Attribute::Name,
618 PartialValue::new_iname("testgroup_a")
619 )),
620 ModifyList::new_list(vec![Modify::Present(
621 Attribute::Uuid,
622 Value::from("f15a7219-1d15-44e3-a7b4-bec899c07788")
623 )]),
624 None,
625 |_| {},
626 |_| {}
627 );
628 }
629
630 #[test]
631 fn test_modify_uuid_removed() {
632 let ea = entry_init!(
634 (Attribute::Class, EntryClass::Group.to_value()),
635 (Attribute::Name, Value::new_iname("testgroup_a")),
636 (
637 Attribute::Uuid,
638 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
639 )
640 );
641
642 let preload = vec![ea];
643
644 run_modify_test!(
645 Err(OperationError::SystemProtectedAttribute),
646 preload,
647 filter!(f_eq(
648 Attribute::Name,
649 PartialValue::new_iname("testgroup_a")
650 )),
651 ModifyList::new_list(vec![Modify::Removed(
652 Attribute::Uuid,
653 PartialValue::Uuid(uuid!("f15a7219-1d15-44e3-a7b4-bec899c07788"))
654 )]),
655 None,
656 |_| {},
657 |_| {}
658 );
659 }
660
661 #[test]
662 fn test_modify_uuid_purged() {
663 let ea = entry_init!(
665 (Attribute::Class, EntryClass::Group.to_value()),
666 (Attribute::Name, Value::new_iname("testgroup_a")),
667 (
668 Attribute::Uuid,
669 Value::Uuid(uuid::uuid!("79724141-3603-4060-b6bb-35c72772611d"))
670 )
671 );
672
673 let preload = vec![ea];
674
675 run_modify_test!(
676 Err(OperationError::SystemProtectedAttribute),
677 preload,
678 filter!(f_eq(
679 Attribute::Name,
680 PartialValue::new_iname("testgroup_a")
681 )),
682 ModifyList::new_list(vec![Modify::Purged(Attribute::Uuid)]),
683 None,
684 |_| {},
685 |_| {}
686 );
687 }
688
689 #[test]
690 fn test_protected_uuid_range() {
691 let preload = PRELOAD.clone();
695
696 let e = entry_init!(
697 (Attribute::Class, EntryClass::Person.to_value()),
698 (Attribute::Name, Value::new_iname("testperson")),
699 (Attribute::DisplayName, Value::new_iname("testperson")),
700 (
701 Attribute::Uuid,
702 Value::Uuid(uuid::uuid!("00000000-0000-0000-0000-f0f0f0f0f0f0"))
703 )
704 );
705
706 let create = vec![e];
707
708 run_create_test!(
709 Err(OperationError::Plugin(PluginError::Base(
710 "Uuid must not be in protected range".to_string()
711 ))),
712 preload,
713 create,
714 Some(E_TEST_ACCOUNT.clone()),
715 |_| {}
716 );
717 }
718
719 #[test]
720 fn test_protected_uuid_range_2() {
721 let preload = PRELOAD.clone();
725
726 let e = entry_init!(
727 (Attribute::Class, EntryClass::Person.to_value()),
728 (Attribute::Name, Value::new_iname("testperson")),
729 (Attribute::DisplayName, Value::new_iname("testperson")),
730 (
731 Attribute::Uuid,
732 Value::Uuid(uuid::uuid!("00000000-0000-0000-0000-f0f0f0f0f0f0"))
733 )
734 );
735
736 let create = vec![e];
737
738 run_create_test!(
739 Err(OperationError::Plugin(PluginError::Base(
740 "Uuid must not be in protected range".to_string()
741 ))),
742 preload,
743 create,
744 Some(E_TEST_ACCOUNT.clone()),
745 |_| {}
746 );
747 }
748
749 #[test]
750 fn test_protected_uuid_does_not_exist() {
751 let preload = Vec::with_capacity(0);
753
754 let e = entry_init!(
755 (Attribute::Class, EntryClass::Person.to_value()),
756 (Attribute::Class, EntryClass::System.to_value()),
757 (Attribute::Name, Value::new_iname("testperson")),
758 (Attribute::DisplayName, Value::new_iname("testperson")),
759 (
760 Attribute::Uuid,
761 Value::Uuid(uuid::uuid!("00000000-0000-0000-0000-fffffffffffe"))
762 )
763 );
764
765 let create = vec![e];
766
767 run_create_test!(
768 Err(OperationError::Plugin(PluginError::Base(
769 "UUID_DOES_NOT_EXIST may not exist!".to_string()
770 ))),
771 preload,
772 create,
773 None,
774 |_| {}
775 );
776 }
777}