1use self::access::{
5 profiles::{
6 AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
7 },
8 AccessControls, AccessControlsReadTransaction, AccessControlsTransaction,
9 AccessControlsWriteTransaction,
10};
11use self::keys::{
12 KeyObject, KeyProvider, KeyProviders, KeyProvidersReadTransaction, KeyProvidersTransaction,
13 KeyProvidersWriteTransaction,
14};
15use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction};
16use crate::filter::{
17 Filter, FilterInvalid, FilterValid, FilterValidResolved, ResolveFilterCache,
18 ResolveFilterCacheReadTxn,
19};
20use crate::plugins::{
21 self,
22 dyngroup::{DynGroup, DynGroupCache},
23 Plugins,
24};
25use crate::prelude::*;
26use crate::repl::cid::Cid;
27use crate::repl::proto::ReplRuvRange;
28use crate::repl::ruv::ReplicationUpdateVectorTransaction;
29use crate::schema::{
30 Schema, SchemaAttribute, SchemaClass, SchemaReadTransaction, SchemaTransaction,
31 SchemaWriteTransaction,
32};
33use crate::value::{CredentialType, EXTRACT_VAL_DN};
34use crate::valueset::*;
35use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
36use concread::cowcell::*;
37use crypto_glue::{hmac_s256::HmacSha256Key, s256::Sha256Output};
38use hashbrown::{HashMap, HashSet};
39use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
40use kanidm_proto::scim_v1::{
41 server::{ScimListResponse, ScimOAuth2ClaimMap, ScimOAuth2ScopeMap, ScimReference},
42 JsonValue, ScimEntryGetQuery, ScimFilter,
43};
44use std::collections::{BTreeMap, BTreeSet};
45use std::num::NonZeroU64;
46use std::str::FromStr;
47use std::sync::Arc;
48use time::OffsetDateTime;
49use tokio::sync::{Semaphore, SemaphorePermit};
50use tracing::trace;
51
52pub(crate) mod access;
53pub mod assert;
54pub mod batch_modify;
55pub mod create;
56pub mod delete;
57pub mod identity;
58pub(crate) mod keys;
59pub(crate) mod migrations;
60pub mod modify;
61pub(crate) mod recycle;
62pub mod scim;
63pub(crate) mod utils;
64
65const RESOLVE_FILTER_CACHE_MAX: usize = 256;
66const RESOLVE_FILTER_CACHE_LOCAL: usize = 8;
67
68#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq)]
69pub(crate) enum ServerPhase {
70 Bootstrap,
71 SchemaReady,
72 DomainInfoReady,
73 Running,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct DomainInfo {
80 pub(crate) d_uuid: Uuid,
81 pub(crate) d_name: String,
82 pub(crate) d_display: String,
83 pub(crate) d_vers: DomainVersion,
84 pub(crate) d_patch_level: u32,
85 pub(crate) d_devel_taint: bool,
86 pub(crate) d_ldap_allow_unix_pw_bind: bool,
87 pub(crate) d_allow_easter_eggs: bool,
88 pub(crate) d_allow_account_recovery: bool,
89 d_image: Option<ImageValue>,
91}
92
93impl DomainInfo {
94 pub fn name(&self) -> &str {
95 self.d_name.as_str()
96 }
97
98 pub fn display_name(&self) -> &str {
99 self.d_display.as_str()
100 }
101
102 pub fn devel_taint(&self) -> bool {
103 self.d_devel_taint
104 }
105
106 pub fn image(&self) -> Option<&ImageValue> {
107 self.d_image.as_ref()
108 }
109
110 pub fn has_custom_image(&self) -> bool {
111 self.d_image.is_some()
112 }
113
114 pub fn allow_easter_eggs(&self) -> bool {
115 self.d_allow_easter_eggs
116 }
117
118 pub fn allow_account_recovery(&self) -> bool {
119 self.d_allow_account_recovery
120 }
121
122 #[cfg(feature = "test")]
123 pub fn new_test() -> CowCell<Self> {
124 concread::cowcell::CowCell::new(Self {
125 d_uuid: Uuid::new_v4(),
126 d_name: "test domain".to_string(),
127 d_display: "Test Domain".to_string(),
128 d_vers: 1,
129 d_patch_level: 0,
130 d_devel_taint: false,
131 d_ldap_allow_unix_pw_bind: false,
132 d_allow_easter_eggs: false,
133 d_allow_account_recovery: false,
134 d_image: None,
135 })
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Default)]
140pub struct SystemConfig {
141 pub(crate) denied_names: HashSet<String>,
142 pub(crate) pw_badlist: HashSet<String>,
143}
144
145#[derive(Clone, Default)]
146pub struct HmacNameHistoryConfig {
147 pub(crate) enabled: bool,
148 pub(crate) key: HmacSha256Key,
149}
150
151#[derive(Clone, Default)]
152pub struct FeatureConfig {
153 pub(crate) hmac_name_history: HmacNameHistoryConfig,
154}
155
156#[derive(Clone)]
157pub struct QueryServer {
158 phase: Arc<CowCell<ServerPhase>>,
159 pub(crate) d_info: Arc<CowCell<DomainInfo>>,
160 system_config: Arc<CowCell<SystemConfig>>,
161 feature_config: Arc<CowCell<FeatureConfig>>,
162 be: Backend,
163 schema: Arc<Schema>,
164 accesscontrols: Arc<AccessControls>,
165 db_tickets: Arc<Semaphore>,
166 read_tickets: Arc<Semaphore>,
167 write_ticket: Arc<Semaphore>,
168 resolve_filter_cache: Arc<ResolveFilterCache>,
169 dyngroup_cache: Arc<CowCell<DynGroupCache>>,
170 cid_max: Arc<CowCell<Cid>>,
171 key_providers: Arc<KeyProviders>,
172}
173
174pub struct QueryServerReadTransaction<'a> {
175 be_txn: BackendReadTransaction<'a>,
176 pub(crate) d_info: CowCellReadTxn<DomainInfo>,
179 system_config: CowCellReadTxn<SystemConfig>,
180 feature_config: CowCellReadTxn<FeatureConfig>,
181 schema: SchemaReadTransaction,
182 accesscontrols: AccessControlsReadTransaction<'a>,
183 key_providers: KeyProvidersReadTransaction,
184 _db_ticket: SemaphorePermit<'a>,
185 _read_ticket: SemaphorePermit<'a>,
186 resolve_filter_cache: ResolveFilterCacheReadTxn<'a>,
187 trim_cid: Cid,
190 txn_name_to_uuid: BTreeMap<String, Uuid>,
191}
192
193unsafe impl Sync for QueryServerReadTransaction<'_> {}
194
195unsafe impl Send for QueryServerReadTransaction<'_> {}
196
197bitflags::bitflags! {
198 #[derive(Copy, Clone, Debug)]
199 pub struct ChangeFlag: u64 {
200 const SCHEMA = 0b0000_0000_0000_0001;
201 const ACP = 0b0000_0000_0000_0010;
202 const OAUTH2 = 0b0000_0000_0000_0100;
203 const DOMAIN = 0b0000_0000_0000_1000;
204 const SYSTEM_CONFIG = 0b0000_0000_0001_0000;
205 const SYNC_AGREEMENT = 0b0000_0000_0010_0000;
206 const KEY_MATERIAL = 0b0000_0000_0100_0000;
207 const APPLICATION = 0b0000_0000_1000_0000;
208 const OAUTH2_CLIENT = 0b0000_0001_0000_0000;
209 const FEATURE = 0b0000_0010_0000_0000;
210 }
211}
212
213pub struct QueryServerWriteTransaction<'a> {
214 committed: bool,
215 phase: CowCellWriteTxn<'a, ServerPhase>,
216 d_info: CowCellWriteTxn<'a, DomainInfo>,
217 system_config: CowCellWriteTxn<'a, SystemConfig>,
218 feature_config: CowCellWriteTxn<'a, FeatureConfig>,
219 curtime: Duration,
220 cid: CowCellWriteTxn<'a, Cid>,
221 trim_cid: Cid,
222 pub(crate) be_txn: BackendWriteTransaction<'a>,
223 pub(crate) schema: SchemaWriteTransaction<'a>,
224 accesscontrols: AccessControlsWriteTransaction<'a>,
225 key_providers: KeyProvidersWriteTransaction<'a>,
226 pub(super) changed_flags: ChangeFlag,
230
231 pub(super) changed_uuid: HashSet<Uuid>,
233 _db_ticket: SemaphorePermit<'a>,
234 _write_ticket: SemaphorePermit<'a>,
235 resolve_filter_cache_clear: bool,
236 resolve_filter_cache_write: ARCacheWriteTxn<
237 'a,
238 (IdentityId, Arc<Filter<FilterValid>>),
239 Arc<Filter<FilterValidResolved>>,
240 (),
241 >,
242 resolve_filter_cache: ARCacheReadTxn<
243 'a,
244 (IdentityId, Arc<Filter<FilterValid>>),
245 Arc<Filter<FilterValidResolved>>,
246 (),
247 >,
248 dyngroup_cache: CowCellWriteTxn<'a, DynGroupCache>,
249 txn_name_to_uuid: BTreeMap<String, Uuid>,
250}
251
252impl QueryServerWriteTransaction<'_> {
253 pub(crate) fn trim_cid(&self) -> &Cid {
254 &self.trim_cid
255 }
256}
257
258pub trait QueryServerTransaction<'a> {
268 type BackendTransactionType: BackendTransaction;
269 fn get_be_txn(&mut self) -> &mut Self::BackendTransactionType;
270
271 type SchemaTransactionType: SchemaTransaction;
272 fn get_schema<'b>(&self) -> &'b Self::SchemaTransactionType;
273
274 type AccessControlsTransactionType: AccessControlsTransaction<'a>;
275 fn get_accesscontrols(&self) -> &Self::AccessControlsTransactionType;
276
277 type KeyProvidersTransactionType: KeyProvidersTransaction;
278 fn get_key_providers(&self) -> &Self::KeyProvidersTransactionType;
279
280 fn pw_badlist(&self) -> &HashSet<String>;
281
282 fn denied_names(&self) -> &HashSet<String>;
283
284 fn domain_info(&self) -> &DomainInfo;
285
286 fn get_domain_version(&self) -> DomainVersion;
287
288 fn get_domain_patch_level(&self) -> u32;
289
290 fn get_domain_development_taint(&self) -> bool;
291
292 fn get_domain_uuid(&self) -> Uuid;
293
294 fn get_domain_name(&self) -> &str;
295
296 fn get_domain_display_name(&self) -> &str;
297
298 fn get_domain_image_value(&self) -> Option<ImageValue>;
299
300 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>>;
301
302 fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig;
303
304 fn txn_name_to_uuid(&mut self) -> &mut BTreeMap<String, Uuid>;
305
306 fn get_resolve_filter_cache_and_be_txn(
310 &mut self,
311 ) -> (
312 &mut Self::BackendTransactionType,
313 Option<&mut ResolveFilterCacheReadTxn<'a>>,
314 );
315
316 #[instrument(level = "debug", skip_all)]
326 fn search_ext(
327 &mut self,
328 se: &SearchEvent,
329 ) -> Result<Vec<EntryReducedCommitted>, OperationError> {
330 let entries = self.search(se)?;
336
337 let access = self.get_accesscontrols();
338 access
339 .search_filter_entry_attributes(se, entries)
340 .map_err(|e| {
341 admin_error!(?e, "Failed to filter entry attributes");
343 e
344 })
345 }
347
348 #[instrument(level = "debug", skip_all)]
349 fn search(
350 &mut self,
351 se: &SearchEvent,
352 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
353 if se.ident.is_internal() {
354 trace!(internal_filter = ?se.filter, "search");
355 } else {
356 security_info!(initiator = %se.ident, "search");
357 admin_debug!(external_filter = ?se.filter, "search");
358 }
359
360 let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
370
371 let idxmeta = be_txn.get_idxmeta_ref();
372
373 trace!(resolve_filter_cache = %resolve_filter_cache.is_some());
374
375 let vfr = se
377 .filter
378 .resolve(&se.ident, Some(idxmeta), resolve_filter_cache)
379 .map_err(|e| {
380 admin_error!(?e, "search filter resolve failure");
381 e
382 })?;
383
384 let lims = se.ident.limits();
385
386 let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
391 admin_error!(?e, "backend failure");
392 OperationError::Backend
393 })?;
394
395 let access = self.get_accesscontrols();
401 access.search_filter_entries(se, res).map_err(|e| {
402 admin_error!(?e, "Unable to access filter entries");
403 e
404 })
405 }
406
407 #[instrument(level = "debug", skip_all)]
408 fn exists(&mut self, ee: &ExistsEvent) -> Result<bool, OperationError> {
409 let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
410 let idxmeta = be_txn.get_idxmeta_ref();
411
412 let vfr = ee
413 .filter
414 .resolve(&ee.ident, Some(idxmeta), resolve_filter_cache)
415 .map_err(|e| {
416 admin_error!(?e, "Failed to resolve filter");
417 e
418 })?;
419
420 let lims = ee.ident.limits();
421
422 if ee.ident.is_internal() {
423 be_txn.exists(lims, &vfr).map_err(|e| {
426 admin_error!(?e, "backend failure");
427 OperationError::Backend
428 })
429 } else {
430 let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
433 admin_error!(?e, "backend failure");
434 OperationError::Backend
435 })?;
436
437 let access = self.get_accesscontrols();
444 access
445 .filter_entries(&ee.ident, &ee.filter_orig, res)
446 .map_err(|e| {
447 admin_error!(?e, "Unable to access filter entries");
448 e
449 })
450 .map(|entries| !entries.is_empty())
451 }
452 }
453
454 fn name_to_uuid(&mut self, name: &str) -> Result<Uuid, OperationError> {
455 let work = EXTRACT_VAL_DN
464 .captures(name)
465 .and_then(|caps| caps.name("val"))
466 .map(|v| v.as_str().to_lowercase())
467 .ok_or(OperationError::InvalidValueState)?;
468
469 if let Ok(uuid) = Uuid::parse_str(&work) {
471 return Ok(uuid);
472 }
473
474 if let Some(uuid) = self.get_be_txn().name2uuid(&work)? {
475 return Ok(uuid);
476 }
477
478 if let Some(uuid) = self.txn_name_to_uuid().get(name) {
479 Ok(*uuid)
480 } else {
481 Err(OperationError::NoMatchingEntries)
482 }
483 }
484
485 fn sync_external_id_to_uuid(
487 &mut self,
488 external_id: &str,
489 ) -> Result<Option<Uuid>, OperationError> {
490 Uuid::parse_str(external_id).map(Some).or_else(|_| {
492 let lname = external_id.to_lowercase();
493 self.get_be_txn().externalid2uuid(lname.as_str())
494 })
495 }
496
497 fn uuid_to_spn(&mut self, uuid: Uuid) -> Result<Option<Value>, OperationError> {
498 let r = self.get_be_txn().uuid2spn(uuid)?;
499
500 if let Some(ref n) = r {
501 debug_assert!(n.is_spn() || n.is_iname());
504 }
505
506 Ok(r)
507 }
508
509 fn uuid_to_rdn(&mut self, uuid: Uuid) -> Result<String, OperationError> {
510 self.get_be_txn()
512 .uuid2rdn(uuid)
513 .map(|v| v.unwrap_or_else(|| format!("uuid={}", uuid.as_hyphenated())))
514 }
515
516 #[instrument(level = "debug", skip_all)]
518 fn internal_exists(&mut self, filter: &Filter<FilterInvalid>) -> Result<bool, OperationError> {
519 let f_valid = filter
521 .validate(self.get_schema())
522 .map_err(OperationError::SchemaViolation)?;
523 let ee = ExistsEvent::new_internal(f_valid);
525 self.exists(&ee)
527 }
528
529 #[instrument(level = "debug", skip_all)]
530 fn internal_exists_uuid(&mut self, uuid: Uuid) -> Result<bool, OperationError> {
531 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
532 self.internal_exists(&filter)
533 }
534
535 #[instrument(level = "debug", skip_all)]
536 fn internal_search(
537 &mut self,
538 filter: Filter<FilterInvalid>,
539 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
540 let f_valid = filter
541 .validate(self.get_schema())
542 .map_err(OperationError::SchemaViolation)?;
543 let se = SearchEvent::new_internal(f_valid);
544 self.search(&se)
545 }
546
547 #[instrument(level = "debug", skip_all)]
548 fn impersonate_search_valid(
549 &mut self,
550 f_valid: Filter<FilterValid>,
551 f_intent_valid: Filter<FilterValid>,
552 event: &Identity,
553 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
554 let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
555 self.search(&se)
556 }
557
558 fn impersonate_search_ext_valid(
560 &mut self,
561 f_valid: Filter<FilterValid>,
562 f_intent_valid: Filter<FilterValid>,
563 event: &Identity,
564 ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
565 let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
566 self.search_ext(&se)
567 }
568
569 fn impersonate_search(
571 &mut self,
572 filter: Filter<FilterInvalid>,
573 filter_intent: Filter<FilterInvalid>,
574 event: &Identity,
575 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
576 let f_valid = filter
577 .validate(self.get_schema())
578 .map_err(OperationError::SchemaViolation)?;
579 let f_intent_valid = filter_intent
580 .validate(self.get_schema())
581 .map_err(OperationError::SchemaViolation)?;
582 self.impersonate_search_valid(f_valid, f_intent_valid, event)
583 }
584
585 #[instrument(level = "debug", skip_all)]
586 fn impersonate_search_ext(
587 &mut self,
588 filter: Filter<FilterInvalid>,
589 filter_intent: Filter<FilterInvalid>,
590 event: &Identity,
591 ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
592 let f_valid = filter
593 .validate(self.get_schema())
594 .map_err(OperationError::SchemaViolation)?;
595 let f_intent_valid = filter_intent
596 .validate(self.get_schema())
597 .map_err(OperationError::SchemaViolation)?;
598 self.impersonate_search_ext_valid(f_valid, f_intent_valid, event)
599 }
600
601 #[instrument(level = "debug", skip_all)]
604 fn internal_search_uuid(
605 &mut self,
606 uuid: Uuid,
607 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
608 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
609 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
610 error!(?e, "Filter Validate - SchemaViolation");
611 OperationError::SchemaViolation(e)
612 })?;
613 let se = SearchEvent::new_internal(f_valid);
614
615 let mut vs = self.search(&se)?;
616 match vs.pop() {
617 Some(entry) if vs.is_empty() => Ok(entry),
618 _ => Err(OperationError::NoMatchingEntries),
619 }
620 }
621
622 #[instrument(level = "debug", skip_all)]
625 fn internal_search_all_uuid(
626 &mut self,
627 uuid: Uuid,
628 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
629 let filter = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
630 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
631 error!(?e, "Filter Validate - SchemaViolation");
632 OperationError::SchemaViolation(e)
633 })?;
634 let se = SearchEvent::new_internal(f_valid);
635
636 let mut vs = self.search(&se)?;
637 match vs.pop() {
638 Some(entry) if vs.is_empty() => Ok(entry),
639 _ => Err(OperationError::NoMatchingEntries),
640 }
641 }
642
643 #[instrument(level = "debug", skip_all)]
645 fn internal_search_conflict_uuid(
646 &mut self,
647 uuid: Uuid,
648 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
649 let filter = filter_all!(f_and(vec![
650 f_eq(Attribute::SourceUuid, PartialValue::Uuid(uuid)),
651 f_eq(Attribute::Class, EntryClass::Conflict.into())
652 ]));
653 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
654 error!(?e, "Filter Validate - SchemaViolation");
655 OperationError::SchemaViolation(e)
656 })?;
657 let se = SearchEvent::new_internal(f_valid);
658
659 self.search(&se)
660 }
661
662 #[instrument(level = "debug", skip_all)]
663 fn impersonate_search_ext_uuid(
664 &mut self,
665 uuid: Uuid,
666 event: &Identity,
667 ) -> Result<Entry<EntryReduced, EntryCommitted>, OperationError> {
668 let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
669 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
670
671 let mut vs = self.impersonate_search_ext(filter, filter_intent, event)?;
672 match vs.pop() {
673 Some(entry) if vs.is_empty() => Ok(entry),
674 _ => {
675 if vs.is_empty() {
676 Err(OperationError::NoMatchingEntries)
677 } else {
678 Err(OperationError::UniqueConstraintViolation)
680 }
681 }
682 }
683 }
684
685 #[instrument(level = "debug", skip_all)]
686 fn impersonate_search_uuid(
687 &mut self,
688 uuid: Uuid,
689 event: &Identity,
690 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
691 let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
692 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
693
694 let mut vs = self.impersonate_search(filter, filter_intent, event)?;
695 match vs.pop() {
696 Some(entry) if vs.is_empty() => Ok(entry),
697 _ => Err(OperationError::NoMatchingEntries),
698 }
699 }
700
701 fn impersonate_uuid_as_readwrite_identity(
706 &mut self,
707 uuid: Uuid,
708 ) -> Result<Identity, OperationError> {
709 self.internal_search_uuid(uuid)
710 .map(Identity::from_impersonate_entry_readwrite)
711 }
712
713 fn clone_value(&mut self, attr: &Attribute, value: &str) -> Result<Value, OperationError> {
716 let schema = self.get_schema();
717
718 match schema.get_attributes().get(attr) {
723 Some(schema_a) => {
724 match schema_a.syntax {
725 SyntaxType::Utf8String => Ok(Value::new_utf8(value.to_string())),
726 SyntaxType::Utf8StringInsensitive => Ok(Value::new_iutf8(value)),
727 SyntaxType::Utf8StringIname => Ok(Value::new_iname(value)),
728 SyntaxType::Boolean => Value::new_bools(value)
729 .ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
730 SyntaxType::SyntaxId => Value::new_syntaxs(value)
731 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
732 SyntaxType::IndexId => Value::new_indexes(value)
733 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
734 SyntaxType::CredentialType => CredentialType::try_from(value)
735 .map(Value::CredentialType)
736 .map_err(|()| OperationError::InvalidAttribute("Invalid CredentialType syntax".to_string())),
737 SyntaxType::Uuid => {
738 let un = self
741 .name_to_uuid(value)
742 .unwrap_or(UUID_DOES_NOT_EXIST);
743 Ok(Value::Uuid(un))
744 }
745 SyntaxType::ReferenceUuid => {
746 let un = self
747 .name_to_uuid(value)
748 .unwrap_or(UUID_DOES_NOT_EXIST);
749 Ok(Value::Refer(un))
750 }
751 SyntaxType::JsonFilter => Value::new_json_filter_s(value)
752 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
753 SyntaxType::Image => Value::new_image(value),
754
755 SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
756 SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
757 SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
758 SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
759 SyntaxType::Uint32 => Value::new_uint32_str(value)
760 .ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
761 SyntaxType::Int64 => Value::new_int64_str(value)
762 .ok_or_else(|| OperationError::InvalidAttribute("Invalid int64 syntax".to_string())),
763 SyntaxType::Uint64 => Value::new_uint64_str(value)
764 .ok_or_else(|| OperationError::InvalidAttribute("Invalid uint64 syntax".to_string())),
765 SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
766 SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
767 .ok_or_else(|| OperationError::InvalidAttribute("Invalid NsUniqueId syntax".to_string())),
768 SyntaxType::DateTime => Value::new_datetime_s(value)
769 .ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
770 SyntaxType::EmailAddress => Value::new_email_address_s(value)
771 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Email Address syntax".to_string())),
772 SyntaxType::Url => Value::new_url_s(value)
773 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Url (whatwg/url) syntax".to_string())),
774 SyntaxType::OauthScope => Value::new_oauthscope(value)
775 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Oauth Scope syntax".to_string())),
776 SyntaxType::WebauthnAttestationCaList => Value::new_webauthn_attestation_ca_list(value)
777 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Webauthn Attestation CA List".to_string())),
778 SyntaxType::OauthScopeMap => Err(OperationError::InvalidAttribute("Oauth Scope Maps can not be supplied through modification - please use the IDM api".to_string())),
779 SyntaxType::OauthClaimMap => Err(OperationError::InvalidAttribute("Oauth Claim Maps can not be supplied through modification - please use the IDM api".to_string())),
780 SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute("Private Binary Values can not be supplied through modification".to_string())),
781 SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
782 SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
783 SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute("AttestedPasskey Values can not be supplied through modification".to_string())),
784 SyntaxType::Session => Err(OperationError::InvalidAttribute("Session Values can not be supplied through modification".to_string())),
785 SyntaxType::ApiToken => Err(OperationError::InvalidAttribute("ApiToken Values can not be supplied through modification".to_string())),
786 SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())),
787 SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())),
788 SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute("Oauth2Session Values can not be supplied through modification".to_string())),
789 SyntaxType::UiHint => UiHint::from_str(value)
790 .map(Value::UiHint)
791 .map_err(|()| OperationError::InvalidAttribute("Invalid uihint syntax".to_string())),
792 SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute("TotpSecret Values can not be supplied through modification".to_string())),
793 SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute("Audit logs are generated and not able to be set.".to_string())),
794 SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute("Ec keys are generated and not able to be set.".to_string())),
795 SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute("Internal keys are generated and not able to be set.".to_string())),
796 SyntaxType::HexString => Value::new_hex_string_s(value)
797 .ok_or_else(|| OperationError::InvalidAttribute("Invalid hex string syntax".to_string())),
798 SyntaxType::Certificate => Value::new_certificate_s(value)
799 .ok_or_else(|| OperationError::InvalidAttribute("Invalid x509 certificate syntax".to_string())),
800 SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute("ApplicationPassword values can not be supplied through modification".to_string())),
801 SyntaxType::Json => Err(OperationError::InvalidAttribute("Json values can not be supplied through modification".to_string())),
802 SyntaxType::Sha256 => Err(OperationError::InvalidAttribute("SHA256 values can not be supplied through modification".to_string())),
803 SyntaxType::Message => Err(OperationError::InvalidAttribute("Message values can not be supplied through modification".to_string())),
804 }
805 }
806 None => {
807 Err(OperationError::InvalidAttributeName(attr.to_string()))
810 }
811 }
812 }
813
814 fn clone_partialvalue(
815 &mut self,
816 attr: &Attribute,
817 value: &str,
818 ) -> Result<PartialValue, OperationError> {
819 let schema = self.get_schema();
820
821 match schema.get_attributes().get(attr) {
823 Some(schema_a) => {
824 match schema_a.syntax {
825 SyntaxType::Utf8String | SyntaxType::TotpSecret => {
826 Ok(PartialValue::new_utf8(value.to_string()))
827 }
828 SyntaxType::Utf8StringInsensitive
829 | SyntaxType::JwsKeyEs256
830 | SyntaxType::JwsKeyRs256 => Ok(PartialValue::new_iutf8(value)),
831 SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
832 SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
833 OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
834 }),
835 SyntaxType::SyntaxId => PartialValue::new_syntaxs(value).ok_or_else(|| {
836 OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
837 }),
838 SyntaxType::IndexId => PartialValue::new_indexes(value).ok_or_else(|| {
839 OperationError::InvalidAttribute("Invalid Index syntax".to_string())
840 }),
841 SyntaxType::CredentialType => CredentialType::try_from(value)
842 .map(PartialValue::CredentialType)
843 .map_err(|()| {
844 OperationError::InvalidAttribute(
845 "Invalid credentialtype syntax".to_string(),
846 )
847 }),
848 SyntaxType::Uuid => {
849 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
850 Ok(PartialValue::Uuid(un))
851 }
852 SyntaxType::ReferenceUuid
856 | SyntaxType::OauthScopeMap
857 | SyntaxType::Session
858 | SyntaxType::ApiToken
859 | SyntaxType::Oauth2Session
860 | SyntaxType::ApplicationPassword => {
861 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
862 Ok(PartialValue::Refer(un))
863 }
864 SyntaxType::OauthClaimMap => self
865 .name_to_uuid(value)
866 .map(PartialValue::Refer)
867 .or_else(|_| Ok(PartialValue::new_iutf8(value))),
868
869 SyntaxType::JsonFilter => {
870 PartialValue::new_json_filter_s(value).ok_or_else(|| {
871 OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
872 })
873 }
874 SyntaxType::Credential => Ok(PartialValue::new_credential_tag(value)),
875 SyntaxType::SecretUtf8String => Ok(PartialValue::new_secret_str()),
876 SyntaxType::SshKey => Ok(PartialValue::new_sshkey_tag_s(value)),
877 SyntaxType::SecurityPrincipalName => {
878 PartialValue::new_spn_s(value).ok_or_else(|| {
879 OperationError::InvalidAttribute("Invalid spn syntax".to_string())
880 })
881 }
882 SyntaxType::Uint32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
883 OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
884 }),
885 SyntaxType::Uint64 => PartialValue::new_uint64_str(value).ok_or_else(|| {
886 OperationError::InvalidAttribute("Invalid uint64 syntax".to_string())
887 }),
888 SyntaxType::Int64 => PartialValue::new_int64_str(value).ok_or_else(|| {
889 OperationError::InvalidAttribute("Invalid int64 syntax".to_string())
890 }),
891 SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
892 OperationError::InvalidAttribute("Invalid cid syntax".to_string())
893 }),
894 SyntaxType::NsUniqueId => Ok(PartialValue::new_nsuniqueid_s(value)),
895 SyntaxType::DateTime => PartialValue::new_datetime_s(value).ok_or_else(|| {
896 OperationError::InvalidAttribute(
897 "Invalid DateTime (rfc3339) syntax".to_string(),
898 )
899 }),
900 SyntaxType::EmailAddress => Ok(PartialValue::new_email_address_s(value)),
901 SyntaxType::Url => PartialValue::new_url_s(value).ok_or_else(|| {
902 OperationError::InvalidAttribute(
903 "Invalid Url (whatwg/url) syntax".to_string(),
904 )
905 }),
906 SyntaxType::OauthScope => Ok(PartialValue::new_oauthscope(value)),
907 SyntaxType::PrivateBinary => Ok(PartialValue::PrivateBinary),
908 SyntaxType::IntentToken => PartialValue::new_intenttoken_s(value.to_string())
909 .ok_or_else(|| {
910 OperationError::InvalidAttribute(
911 "Invalid Intent Token ID (uuid) syntax".to_string(),
912 )
913 }),
914 SyntaxType::Passkey => PartialValue::new_passkey_s(value).ok_or_else(|| {
915 OperationError::InvalidAttribute("Invalid Passkey UUID syntax".to_string())
916 }),
917 SyntaxType::AttestedPasskey => PartialValue::new_attested_passkey_s(value)
918 .ok_or_else(|| {
919 OperationError::InvalidAttribute(
920 "Invalid AttestedPasskey UUID syntax".to_string(),
921 )
922 }),
923 SyntaxType::UiHint => UiHint::from_str(value)
924 .map(PartialValue::UiHint)
925 .map_err(|()| {
926 OperationError::InvalidAttribute("Invalid uihint syntax".to_string())
927 }),
928 SyntaxType::AuditLogString => Ok(PartialValue::new_utf8s(value)),
929 SyntaxType::EcKeyPrivate => Ok(PartialValue::SecretValue),
930 SyntaxType::Image => Ok(PartialValue::new_utf8s(value)),
931 SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
932 "Invalid - unable to query attestation CA list".to_string(),
933 )),
934 SyntaxType::HexString | SyntaxType::KeyInternal | SyntaxType::Certificate => {
935 PartialValue::new_hex_string_s(value).ok_or_else(|| {
936 OperationError::InvalidAttribute(
937 "Invalid syntax, expected hex string".to_string(),
938 )
939 })
940 }
941 SyntaxType::Sha256 => {
942 let mut sha256bytes = Sha256Output::default();
943 if hex::decode_to_slice(value, &mut sha256bytes).is_ok() {
944 Ok(PartialValue::Sha256(sha256bytes))
945 } else {
946 Err(OperationError::InvalidAttribute(
947 "Invalid syntax, expected sha256 hex string containing 64 characters".to_string(),
948 ))
949 }
950 }
951 SyntaxType::Json => Err(OperationError::InvalidAttribute(
952 "Json values can not be validated by this interface".to_string(),
953 )),
954 SyntaxType::Message => Err(OperationError::InvalidAttribute(
955 "Message values can not be validated by this interface".to_string(),
956 )),
957 }
958 }
959 None => {
960 Err(OperationError::InvalidAttributeName(attr.to_string()))
963 }
964 }
965 }
966
967 fn resolve_scim_interim(
968 &mut self,
969 scim_value_intermediate: ScimValueIntermediate,
970 ) -> Result<Option<ScimValueKanidm>, OperationError> {
971 match scim_value_intermediate {
972 ScimValueIntermediate::References(uuids) => {
973 let scim_references = uuids
974 .into_iter()
975 .map(|uuid| {
976 self.uuid_to_spn(uuid)
977 .and_then(|maybe_value| {
978 maybe_value.ok_or(OperationError::InvalidValueState)
979 })
980 .map(|value| ScimReference {
981 uuid,
982 value: value.to_proto_string_clone(),
983 })
984 })
985 .collect::<Result<Vec<_>, _>>()?;
986 Ok(Some(ScimValueKanidm::EntryReferences(scim_references)))
987 }
988 ScimValueIntermediate::Oauth2ClaimMap(unresolved_maps) => {
989 let scim_claim_maps = unresolved_maps
990 .into_iter()
991 .map(
992 |UnresolvedScimValueOauth2ClaimMap {
993 group_uuid,
994 claim,
995 join_char,
996 values,
997 }| {
998 self.uuid_to_spn(group_uuid)
999 .and_then(|maybe_value| {
1000 maybe_value.ok_or(OperationError::InvalidValueState)
1001 })
1002 .map(|value| ScimOAuth2ClaimMap {
1003 group: value.to_proto_string_clone(),
1004 group_uuid,
1005 claim,
1006 join_char,
1007 values,
1008 })
1009 },
1010 )
1011 .collect::<Result<Vec<_>, _>>()?;
1012
1013 Ok(Some(ScimValueKanidm::OAuth2ClaimMap(scim_claim_maps)))
1014 }
1015
1016 ScimValueIntermediate::Oauth2ScopeMap(unresolved_maps) => {
1017 let scim_claim_maps = unresolved_maps
1018 .into_iter()
1019 .map(|UnresolvedScimValueOauth2ScopeMap { group_uuid, scopes }| {
1020 self.uuid_to_spn(group_uuid)
1021 .and_then(|maybe_value| {
1022 maybe_value.ok_or(OperationError::InvalidValueState)
1023 })
1024 .map(|value| ScimOAuth2ScopeMap {
1025 group: value.to_proto_string_clone(),
1026 group_uuid,
1027 scopes,
1028 })
1029 })
1030 .collect::<Result<Vec<_>, _>>()?;
1031
1032 Ok(Some(ScimValueKanidm::OAuth2ScopeMap(scim_claim_maps)))
1033 }
1034 }
1035 }
1036
1037 fn resolve_scim_json_get(
1038 &mut self,
1039 attr: &Attribute,
1040 value: &JsonValue,
1041 ) -> Result<PartialValue, OperationError> {
1042 let schema = self.get_schema();
1043 let Some(schema_a) = schema.get_attributes().get(attr) else {
1045 return Err(OperationError::InvalidAttributeName(attr.to_string()));
1048 };
1049
1050 debug!(schema_syntax = ?schema_a.syntax, ?value);
1051
1052 match schema_a.syntax {
1053 SyntaxType::Utf8String => {
1054 let JsonValue::String(value) = value else {
1055 return Err(OperationError::InvalidAttribute(attr.to_string()));
1056 };
1057 Ok(PartialValue::Utf8(value.to_string()))
1058 }
1059 SyntaxType::Utf8StringInsensitive => {
1060 let JsonValue::String(value) = value else {
1061 return Err(OperationError::InvalidAttribute(attr.to_string()));
1062 };
1063 Ok(PartialValue::new_iutf8(value))
1064 }
1065 SyntaxType::Utf8StringIname => {
1066 let JsonValue::String(value) = value else {
1067 return Err(OperationError::InvalidAttribute(attr.to_string()));
1068 };
1069 Ok(PartialValue::new_iname(value))
1070 }
1071 SyntaxType::Uuid => {
1072 let JsonValue::String(value) = value else {
1073 return Err(OperationError::InvalidAttribute(attr.to_string()));
1074 };
1075
1076 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1077 Ok(PartialValue::Uuid(un))
1078 }
1079 SyntaxType::Boolean => {
1080 let JsonValue::Bool(value) = value else {
1081 return Err(OperationError::InvalidAttribute(attr.to_string()));
1082 };
1083 Ok(PartialValue::Bool(*value))
1084 }
1085 SyntaxType::SyntaxId => {
1086 let JsonValue::String(value) = value else {
1087 return Err(OperationError::InvalidAttribute(attr.to_string()));
1088 };
1089 let Ok(value) = SyntaxType::try_from(value.as_str()) else {
1090 return Err(OperationError::InvalidAttribute(attr.to_string()));
1091 };
1092 Ok(PartialValue::Syntax(value))
1093 }
1094 SyntaxType::ReferenceUuid
1095 | SyntaxType::OauthScopeMap
1096 | SyntaxType::Session
1097 | SyntaxType::ApiToken
1098 | SyntaxType::Oauth2Session
1099 | SyntaxType::ApplicationPassword => {
1100 let JsonValue::String(value) = value else {
1101 return Err(OperationError::InvalidAttribute(attr.to_string()));
1102 };
1103
1104 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1105 Ok(PartialValue::Refer(un))
1106 }
1107
1108 _ => Err(OperationError::InvalidAttribute(attr.to_string())),
1109 }
1110 }
1111
1112 fn resolve_valueset_intermediate(
1113 &mut self,
1114 vs_inter: ValueSetIntermediate,
1115 ) -> Result<ValueSet, OperationError> {
1116 match vs_inter {
1117 ValueSetIntermediate::References {
1118 mut resolved,
1119 unresolved,
1120 } => {
1121 for value in unresolved {
1122 let un = self.name_to_uuid(value.as_str()).unwrap_or_else(|_| {
1123 warn!(
1124 ?value,
1125 "Value can not be resolved to a uuid - assuming it does not exist."
1126 );
1127 UUID_DOES_NOT_EXIST
1128 });
1129
1130 resolved.insert(un);
1131 }
1132
1133 let vs = ValueSetRefer::from_set(resolved);
1134 Ok(vs)
1135 }
1136
1137 ValueSetIntermediate::Oauth2ClaimMap {
1138 mut resolved,
1139 unresolved,
1140 } => {
1141 resolved.extend(unresolved.into_iter().map(
1142 |UnresolvedValueSetOauth2ClaimMap {
1143 group_name,
1144 claim,
1145 join_char,
1146 claim_values,
1147 }| {
1148 let group_uuid =
1149 self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1150 warn!(
1151 ?group_name,
1152 "Value can not be resolved to a uuid - assuming it does not exist."
1153 );
1154 UUID_DOES_NOT_EXIST
1155 });
1156
1157 ResolvedValueSetOauth2ClaimMap {
1158 group_uuid,
1159 claim,
1160 join_char,
1161 claim_values,
1162 }
1163 },
1164 ));
1165
1166 let vs = ValueSetOauthClaimMap::from_set(resolved);
1167 Ok(vs)
1168 }
1169
1170 ValueSetIntermediate::Oauth2ScopeMap {
1171 mut resolved,
1172 unresolved,
1173 } => {
1174 resolved.extend(unresolved.into_iter().map(
1175 |UnresolvedValueSetOauth2ScopeMap { group_name, scopes }| {
1176 let group_uuid =
1177 self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1178 warn!(
1179 ?group_name,
1180 "Value can not be resolved to a uuid - assuming it does not exist."
1181 );
1182 UUID_DOES_NOT_EXIST
1183 });
1184
1185 ResolvedValueSetOauth2ScopeMap { group_uuid, scopes }
1186 },
1187 ));
1188
1189 let vs = ValueSetOauthScopeMap::from_set(resolved);
1190 Ok(vs)
1191 }
1192 }
1193 }
1194
1195 fn resolve_valueset(&mut self, value: &ValueSet) -> Result<Vec<String>, OperationError> {
1197 if let Some(r_set) = value.as_refer_set() {
1198 let v: Result<Vec<_>, _> = r_set
1199 .iter()
1200 .copied()
1201 .map(|ur| {
1202 let nv = self.uuid_to_spn(ur)?;
1203 match nv {
1204 Some(v) => Ok(v.to_proto_string_clone()),
1205 None => Ok(uuid_to_proto_string(ur)),
1206 }
1207 })
1208 .collect();
1209 v
1210 } else if let Some(r_map) = value.as_oauthscopemap() {
1211 let v: Result<Vec<_>, _> = r_map
1212 .iter()
1213 .map(|(u, m)| {
1214 let nv = self.uuid_to_spn(*u)?;
1215 let u = match nv {
1216 Some(v) => v.to_proto_string_clone(),
1217 None => uuid_to_proto_string(*u),
1218 };
1219 Ok(format!("{u}: {m:?}"))
1220 })
1221 .collect();
1222 v
1223 } else if let Some(r_map) = value.as_oauthclaim_map() {
1224 let mut v = Vec::with_capacity(0);
1225 for (claim_name, mapping) in r_map.iter() {
1226 for (group_ref, claims) in mapping.values() {
1227 let join_char = mapping.join().to_str();
1228
1229 let nv = self.uuid_to_spn(*group_ref)?;
1230 let resolved_id = match nv {
1231 Some(v) => v.to_proto_string_clone(),
1232 None => uuid_to_proto_string(*group_ref),
1233 };
1234
1235 let joined = str_concat!(claims, ",");
1236
1237 v.push(format!("{claim_name}:{resolved_id}:{join_char}:{joined:?}"))
1238 }
1239 }
1240 Ok(v)
1241 } else {
1242 let v: Vec<_> = value.to_proto_string_clone_iter().collect();
1243 Ok(v)
1244 }
1245 }
1246
1247 fn resolve_valueset_ldap(
1248 &mut self,
1249 value: &ValueSet,
1250 basedn: &str,
1251 ) -> Result<Vec<Vec<u8>>, OperationError> {
1252 if let Some(r_set) = value.as_refer_set() {
1253 let v: Result<Vec<_>, _> = r_set
1254 .iter()
1255 .copied()
1256 .map(|ur| {
1257 let rdn = self.uuid_to_rdn(ur)?;
1258 Ok(format!("{rdn},{basedn}").into_bytes())
1259 })
1260 .collect();
1261 v
1262 } else if let Some(key_iter) = value.as_sshpubkey_string_iter() {
1265 let v: Vec<_> = key_iter.map(|s| s.into_bytes()).collect();
1266 Ok(v)
1267 } else {
1268 let v: Vec<_> = value
1269 .to_proto_string_clone_iter()
1270 .map(|s| s.into_bytes())
1271 .collect();
1272 Ok(v)
1273 }
1274 }
1275
1276 fn get_db_domain(&mut self) -> Result<Arc<EntrySealedCommitted>, OperationError> {
1277 self.internal_search_uuid(UUID_DOMAIN_INFO)
1278 }
1279
1280 fn get_domain_key_object_handle(&self) -> Result<Arc<KeyObject>, OperationError> {
1281 self.get_key_providers()
1282 .get_key_object_handle(UUID_DOMAIN_INFO)
1283 .ok_or(OperationError::KP0031KeyObjectNotFound)
1284 }
1285
1286 fn get_domain_es256_private_key(&mut self) -> Result<Vec<u8>, OperationError> {
1287 self.internal_search_uuid(UUID_DOMAIN_INFO)
1288 .and_then(|e| {
1289 e.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
1290 .map(|s| s.to_vec())
1291 .ok_or(OperationError::InvalidEntryState)
1292 })
1293 .map_err(|e| {
1294 admin_error!(?e, "Error getting domain es256 key");
1295 e
1296 })
1297 }
1298
1299 fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
1300 self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| {
1301 entry
1302 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
1303 .unwrap_or(true)
1304 })
1305 }
1306
1307 fn get_sc_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
1310 self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1311 .map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) {
1312 Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1313 None => HashSet::default(),
1314 })
1315 .map_err(|e| {
1316 error!(
1317 ?e,
1318 "Failed to retrieve password badlist from system configuration"
1319 );
1320 e
1321 })
1322 }
1323
1324 fn get_sc_denied_names(&mut self) -> Result<HashSet<String>, OperationError> {
1327 self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1328 .map(|e| match e.get_ava_iter_iname(Attribute::DeniedName) {
1329 Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1330 None => HashSet::default(),
1331 })
1332 .map_err(|e| {
1333 error!(
1334 ?e,
1335 "Failed to retrieve denied names from system configuration"
1336 );
1337 e
1338 })
1339 }
1340
1341 fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1342 self.internal_search(filter!(f_eq(
1343 Attribute::Class,
1344 EntryClass::OAuth2ResourceServer.into(),
1345 )))
1346 }
1347
1348 fn get_applications_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1349 self.internal_search(filter!(f_eq(
1350 Attribute::Class,
1351 EntryClass::Application.into(),
1352 )))
1353 }
1354
1355 #[instrument(level = "debug", skip_all)]
1356 fn consumer_get_state(&mut self) -> Result<ReplRuvRange, OperationError> {
1357 let domain_uuid = self.get_domain_uuid();
1382
1383 let ruv_snapshot = self.get_be_txn().get_ruv();
1386
1387 ruv_snapshot
1389 .current_ruv_range()
1390 .map(|ranges| ReplRuvRange::V1 {
1391 domain_uuid,
1392 ranges,
1393 })
1394 }
1395}
1396
1397impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
1401 type AccessControlsTransactionType = AccessControlsReadTransaction<'a>;
1402 type BackendTransactionType = BackendReadTransaction<'a>;
1403 type SchemaTransactionType = SchemaReadTransaction;
1404 type KeyProvidersTransactionType = KeyProvidersReadTransaction;
1405
1406 fn get_be_txn(&mut self) -> &mut BackendReadTransaction<'a> {
1407 &mut self.be_txn
1408 }
1409
1410 fn get_schema<'b>(&self) -> &'b SchemaReadTransaction {
1411 unsafe {
1415 let s = (&self.schema) as *const _;
1416 &*s
1417 }
1418 }
1419
1420 fn get_accesscontrols(&self) -> &AccessControlsReadTransaction<'a> {
1421 &self.accesscontrols
1422 }
1423
1424 fn get_key_providers(&self) -> &KeyProvidersReadTransaction {
1425 &self.key_providers
1426 }
1427
1428 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1429 Some(&mut self.resolve_filter_cache)
1430 }
1431
1432 fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig {
1433 &self.feature_config.hmac_name_history
1434 }
1435
1436 fn txn_name_to_uuid(&mut self) -> &mut BTreeMap<String, Uuid> {
1437 &mut self.txn_name_to_uuid
1438 }
1439
1440 fn get_resolve_filter_cache_and_be_txn(
1441 &mut self,
1442 ) -> (
1443 &mut BackendReadTransaction<'a>,
1444 Option<&mut ResolveFilterCacheReadTxn<'a>>,
1445 ) {
1446 (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1447 }
1448
1449 fn pw_badlist(&self) -> &HashSet<String> {
1450 &self.system_config.pw_badlist
1451 }
1452
1453 fn denied_names(&self) -> &HashSet<String> {
1454 &self.system_config.denied_names
1455 }
1456
1457 fn domain_info(&self) -> &DomainInfo {
1458 &self.d_info
1459 }
1460
1461 fn get_domain_version(&self) -> DomainVersion {
1462 self.d_info.d_vers
1463 }
1464
1465 fn get_domain_patch_level(&self) -> u32 {
1466 self.d_info.d_patch_level
1467 }
1468
1469 fn get_domain_development_taint(&self) -> bool {
1470 self.d_info.d_devel_taint
1471 }
1472
1473 fn get_domain_uuid(&self) -> Uuid {
1474 self.d_info.d_uuid
1475 }
1476
1477 fn get_domain_name(&self) -> &str {
1478 &self.d_info.d_name
1479 }
1480
1481 fn get_domain_display_name(&self) -> &str {
1482 &self.d_info.d_display
1483 }
1484
1485 fn get_domain_image_value(&self) -> Option<ImageValue> {
1486 self.d_info.d_image.clone()
1487 }
1488}
1489
1490impl QueryServerReadTransaction<'_> {
1491 pub(crate) fn trim_cid(&self) -> &Cid {
1492 &self.trim_cid
1493 }
1494
1495 pub fn public_domain_info(&mut self) -> Result<ProtoDomainInfo, OperationError> {
1497 let d_info = &self.d_info;
1498
1499 Ok(ProtoDomainInfo {
1500 name: d_info.d_name.clone(),
1501 displayname: d_info.d_display.clone(),
1502 uuid: d_info.d_uuid,
1503 level: d_info.d_vers,
1504 })
1505 }
1506
1507 pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
1511 let be_errs = self.get_be_txn().verify();
1515
1516 if !be_errs.is_empty() {
1517 return be_errs;
1518 }
1519
1520 let sc_errs = self.get_schema().validate();
1522
1523 if !sc_errs.is_empty() {
1524 return sc_errs;
1525 }
1526
1527 let idx_errs = self.get_be_txn().verify_indexes();
1531
1532 if !idx_errs.is_empty() {
1533 return idx_errs;
1534 }
1535
1536 let mut results = Vec::with_capacity(0);
1539
1540 let schema = self.get_schema();
1543
1544 let filt_all = filter!(f_pres(Attribute::Class));
1545 let all_entries = match self.internal_search(filt_all) {
1546 Ok(a) => a,
1547 Err(_e) => return vec![Err(ConsistencyError::QueryServerSearchFailure)],
1548 };
1549
1550 for e in all_entries {
1551 e.verify(schema, &mut results)
1552 }
1553
1554 self.get_be_txn().verify_ruv(&mut results);
1556
1557 Plugins::run_verify(self, &mut results);
1563 results
1566 }
1567
1568 #[instrument(level = "debug", skip_all)]
1569 pub fn scim_entry_id_get_ext(
1570 &mut self,
1571 uuid: Uuid,
1572 class: EntryClass,
1573 query: ScimEntryGetQuery,
1574 ident: Identity,
1575 ) -> Result<ScimEntryKanidm, OperationError> {
1576 let filter_intent = filter!(f_and!([
1577 f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)),
1578 f_eq(Attribute::Class, class.into())
1579 ]));
1580
1581 let f_intent_valid = filter_intent
1582 .validate(self.get_schema())
1583 .map_err(OperationError::SchemaViolation)?;
1584
1585 let f_valid = f_intent_valid.clone().into_ignore_hidden();
1586
1587 let r_attrs = query
1588 .attributes
1589 .map(|attr_set| attr_set.into_iter().collect());
1590
1591 let se = SearchEvent {
1592 ident,
1593 filter: f_valid,
1594 filter_orig: f_intent_valid,
1595 attrs: r_attrs,
1596 effective_access_check: query.ext_access_check,
1597 };
1598
1599 let mut vs = self.search_ext(&se)?;
1600 match vs.pop() {
1601 Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
1602 _ => {
1603 if vs.is_empty() {
1604 Err(OperationError::NoMatchingEntries)
1605 } else {
1606 Err(OperationError::UniqueConstraintViolation)
1608 }
1609 }
1610 }
1611 }
1612
1613 #[instrument(level = "debug", skip_all)]
1614 pub fn scim_search_ext(
1615 &mut self,
1616 ident: Identity,
1617 filter: ScimFilter,
1618 query: ScimEntryGetQuery,
1619 ) -> Result<ScimListResponse, OperationError> {
1620 let filter = if let Some(ref user_filter) = query.filter {
1621 ScimFilter::And(Box::new(filter), Box::new(user_filter.clone()))
1622 } else {
1623 filter
1624 };
1625
1626 let filter_intent = Filter::from_scim_ro(&ident, &filter, self)?;
1627
1628 self.scim_search_filter_ext(ident, &filter_intent, query)
1629 }
1630
1631 pub fn scim_search_filter_ext(
1632 &mut self,
1633 ident: Identity,
1634 filter_intent: &Filter<FilterInvalid>,
1635 query: ScimEntryGetQuery,
1636 ) -> Result<ScimListResponse, OperationError> {
1637 let f_intent_valid = filter_intent
1638 .validate(self.get_schema())
1639 .map_err(OperationError::SchemaViolation)?;
1640
1641 let f_valid = f_intent_valid.clone().into_ignore_hidden();
1642
1643 let r_attrs = query
1644 .attributes
1645 .map(|attr_set| attr_set.into_iter().collect());
1646
1647 let se = SearchEvent {
1648 ident,
1649 filter: f_valid,
1650 filter_orig: f_intent_valid,
1651 attrs: r_attrs,
1652 effective_access_check: query.ext_access_check,
1653 };
1654
1655 let mut result_set = self.search_ext(&se)?;
1656
1657 let total_results = result_set.len() as u64;
1659
1660 if let Some(sort_attr) = query.sort_by {
1666 result_set.sort_unstable_by(|entry_left, entry_right| {
1667 let left = entry_left.get_ava_set(&sort_attr);
1668 let right = entry_right.get_ava_set(&sort_attr);
1669 match (left, right) {
1670 (Some(left), Some(right)) => left.cmp(right),
1671 (Some(_), None) => std::cmp::Ordering::Less,
1672 (None, Some(_)) => std::cmp::Ordering::Greater,
1673 (None, None) => std::cmp::Ordering::Equal,
1674 }
1675 });
1676 }
1677
1678 let (items_per_page, start_index, paginated_result_set) = if let Some(count) = query.count {
1680 let count: u64 = count.get();
1681 let start_index: u64 = query
1684 .start_index
1685 .map(|non_zero_index|
1686 non_zero_index.get() - 1)
1688 .unwrap_or_default();
1689
1690 if start_index as usize > result_set.len() {
1692 return Err(OperationError::SC0029PaginationOutOfBounds);
1695 }
1696
1697 let mut result_set = result_set.split_off(start_index as usize);
1698 result_set.truncate(count as usize);
1699
1700 (
1701 NonZeroU64::new(count),
1702 NonZeroU64::new(start_index + 1),
1703 result_set,
1704 )
1705 } else {
1706 (None, None, result_set)
1708 };
1709
1710 let resources = paginated_result_set
1711 .into_iter()
1712 .map(|entry| entry.to_scim_kanidm(self))
1713 .collect::<Result<Vec<_>, _>>()?;
1714
1715 Ok(ScimListResponse {
1716 schemas: Vec::with_capacity(0),
1718 total_results,
1719 items_per_page,
1720 start_index,
1721 resources,
1722 })
1723 }
1724
1725 #[instrument(level = "debug", skip_all)]
1726 pub fn scim_search_message_ready_ext(
1727 &mut self,
1728 ident: Identity,
1729 curtime: Duration,
1730 ) -> Result<ScimListResponse, OperationError> {
1731 let curtime_odt = OffsetDateTime::UNIX_EPOCH + curtime;
1732
1733 let filter_intent = filter_all!(f_and(vec![
1734 f_eq(Attribute::Class, EntryClass::OutboundMessage.into()),
1735 f_lt(Attribute::SendAfter, PartialValue::DateTime(curtime_odt)),
1736 f_andnot(f_pres(Attribute::SentAt))
1737 ]));
1738
1739 let query = ScimEntryGetQuery::default();
1740
1741 self.scim_search_filter_ext(ident, &filter_intent, query)
1742 }
1743}
1744
1745impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
1746 type AccessControlsTransactionType = AccessControlsWriteTransaction<'a>;
1747 type BackendTransactionType = BackendWriteTransaction<'a>;
1748 type SchemaTransactionType = SchemaWriteTransaction<'a>;
1749 type KeyProvidersTransactionType = KeyProvidersWriteTransaction<'a>;
1750
1751 fn get_be_txn(&mut self) -> &mut BackendWriteTransaction<'a> {
1752 &mut self.be_txn
1753 }
1754
1755 fn get_schema<'b>(&self) -> &'b SchemaWriteTransaction<'a> {
1756 unsafe {
1760 let s = (&self.schema) as *const _;
1761 &*s
1762 }
1763 }
1764
1765 fn get_accesscontrols(&self) -> &AccessControlsWriteTransaction<'a> {
1766 &self.accesscontrols
1767 }
1768
1769 fn get_key_providers(&self) -> &KeyProvidersWriteTransaction<'a> {
1770 &self.key_providers
1771 }
1772
1773 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1774 if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1775 None
1776 } else {
1777 Some(&mut self.resolve_filter_cache)
1778 }
1779 }
1780
1781 fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig {
1782 &self.feature_config.hmac_name_history
1783 }
1784
1785 fn txn_name_to_uuid(&mut self) -> &mut BTreeMap<String, Uuid> {
1786 &mut self.txn_name_to_uuid
1787 }
1788
1789 fn get_resolve_filter_cache_and_be_txn(
1790 &mut self,
1791 ) -> (
1792 &mut BackendWriteTransaction<'a>,
1793 Option<&mut ResolveFilterCacheReadTxn<'a>>,
1794 ) {
1795 if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1796 (&mut self.be_txn, None)
1797 } else {
1798 (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1799 }
1800 }
1801
1802 fn pw_badlist(&self) -> &HashSet<String> {
1803 &self.system_config.pw_badlist
1804 }
1805
1806 fn denied_names(&self) -> &HashSet<String> {
1807 &self.system_config.denied_names
1808 }
1809
1810 fn domain_info(&self) -> &DomainInfo {
1811 &self.d_info
1812 }
1813
1814 fn get_domain_version(&self) -> DomainVersion {
1815 self.d_info.d_vers
1816 }
1817
1818 fn get_domain_patch_level(&self) -> u32 {
1819 self.d_info.d_patch_level
1820 }
1821
1822 fn get_domain_development_taint(&self) -> bool {
1823 self.d_info.d_devel_taint
1824 }
1825
1826 fn get_domain_uuid(&self) -> Uuid {
1827 self.d_info.d_uuid
1828 }
1829
1830 fn get_domain_name(&self) -> &str {
1832 &self.d_info.d_name
1833 }
1834
1835 fn get_domain_display_name(&self) -> &str {
1836 &self.d_info.d_display
1837 }
1838
1839 fn get_domain_image_value(&self) -> Option<ImageValue> {
1840 self.d_info.d_image.clone()
1841 }
1842}
1843
1844impl QueryServer {
1845 pub fn new(
1846 be: Backend,
1847 schema: Schema,
1848 domain_name: String,
1849 curtime: Duration,
1850 ) -> Result<Self, OperationError> {
1851 let (s_uuid, d_uuid, ts_max) = {
1852 let mut wr = be.write()?;
1853 let s_uuid = wr.get_db_s_uuid()?;
1854 let d_uuid = wr.get_db_d_uuid()?;
1855 let ts_max = wr.get_db_ts_max(curtime)?;
1856 wr.commit()?;
1857 (s_uuid, d_uuid, ts_max)
1858 };
1859
1860 let pool_size = be.get_pool_size();
1861
1862 debug!("Server UUID -> {:?}", s_uuid);
1863 debug!("Domain UUID -> {:?}", d_uuid);
1864 debug!("Domain Name -> {:?}", domain_name);
1865
1866 let d_info = Arc::new(CowCell::new(DomainInfo {
1867 d_uuid,
1868 d_vers: DOMAIN_LEVEL_0,
1871 d_patch_level: 0,
1872 d_name: domain_name.clone(),
1873 d_display: domain_name,
1876 d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(),
1878 d_ldap_allow_unix_pw_bind: false,
1879 d_allow_easter_eggs: false,
1880 d_allow_account_recovery: false,
1881 d_image: None,
1882 }));
1883
1884 let cid = Cid::new_lamport(s_uuid, curtime, &ts_max);
1885 let cid_max = Arc::new(CowCell::new(cid));
1886
1887 let system_config = Arc::new(CowCell::new(SystemConfig::default()));
1889
1890 let feature_config = Arc::new(CowCell::new(FeatureConfig::default()));
1891
1892 let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
1893
1894 let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
1895
1896 let resolve_filter_cache = Arc::new(
1897 ARCacheBuilder::new()
1898 .set_size(RESOLVE_FILTER_CACHE_MAX, RESOLVE_FILTER_CACHE_LOCAL)
1899 .set_reader_quiesce(true)
1900 .build()
1901 .ok_or_else(|| {
1902 error!("Failed to build filter resolve cache");
1903 OperationError::DB0003FilterResolveCacheBuild
1904 })?,
1905 );
1906
1907 let key_providers = Arc::new(KeyProviders::default());
1908
1909 debug_assert!(pool_size > 0);
1912 let read_ticket_pool = std::cmp::max(pool_size - 1, 1);
1913
1914 Ok(QueryServer {
1915 phase,
1916 d_info,
1917 system_config,
1918 feature_config,
1919 be,
1920 schema: Arc::new(schema),
1921 accesscontrols: Arc::new(AccessControls::default()),
1922 db_tickets: Arc::new(Semaphore::new(pool_size as usize)),
1923 read_tickets: Arc::new(Semaphore::new(read_ticket_pool as usize)),
1924 write_ticket: Arc::new(Semaphore::new(1)),
1925 resolve_filter_cache,
1926 dyngroup_cache,
1927 cid_max,
1928 key_providers,
1929 })
1930 }
1931
1932 pub fn try_quiesce(&self) {
1933 self.be.try_quiesce();
1934 self.accesscontrols.try_quiesce();
1935 self.resolve_filter_cache.try_quiesce();
1936 }
1937
1938 #[instrument(level = "debug", skip_all)]
1939 async fn read_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
1940 let read_ticket = if cfg!(test) {
1944 self.read_tickets
1945 .try_acquire()
1946 .inspect_err(|err| {
1947 error!(?err, "Unable to acquire read ticket!");
1948 })
1949 .ok()?
1950 } else {
1951 let fut = tokio::time::timeout(
1952 Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
1953 self.read_tickets.acquire(),
1954 );
1955
1956 match fut.await {
1957 Ok(Ok(ticket)) => ticket,
1958 Ok(Err(_)) => {
1959 error!("Failed to acquire read ticket, may be poisoned.");
1960 return None;
1961 }
1962 Err(_) => {
1963 error!("Failed to acquire read ticket, server is overloaded.");
1964 return None;
1965 }
1966 }
1967 };
1968
1969 let db_ticket = if cfg!(test) {
1974 self.db_tickets
1975 .try_acquire()
1976 .inspect_err(|err| {
1977 error!(?err, "Unable to acquire database ticket!");
1978 })
1979 .ok()?
1980 } else {
1981 self.db_tickets
1982 .acquire()
1983 .await
1984 .inspect_err(|err| {
1985 error!(?err, "Unable to acquire database ticket!");
1986 })
1987 .ok()?
1988 };
1989
1990 Some((read_ticket, db_ticket))
1991 }
1992
1993 pub async fn read(&self) -> Result<QueryServerReadTransaction<'_>, OperationError> {
1994 let (read_ticket, db_ticket) = self
1995 .read_acquire_ticket()
1996 .await
1997 .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
1998 let schema = self.schema.read();
2002
2003 let cid_max = self.cid_max.read();
2004 let trim_cid = cid_max.sub_secs(CHANGELOG_MAX_AGE)?;
2005
2006 let be_txn = self.be.read()?;
2007
2008 Ok(QueryServerReadTransaction {
2009 be_txn,
2010 schema,
2011 d_info: self.d_info.read(),
2012 system_config: self.system_config.read(),
2013 feature_config: self.feature_config.read(),
2014 accesscontrols: self.accesscontrols.read(),
2015 key_providers: self.key_providers.read(),
2016 _db_ticket: db_ticket,
2017 _read_ticket: read_ticket,
2018 resolve_filter_cache: self.resolve_filter_cache.read(),
2019 trim_cid,
2020 txn_name_to_uuid: Default::default(),
2021 })
2022 }
2023
2024 #[instrument(level = "debug", skip_all)]
2025 async fn write_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
2026 let write_ticket = if cfg!(test) {
2028 self.write_ticket
2029 .try_acquire()
2030 .inspect_err(|err| {
2031 error!(?err, "Unable to acquire write ticket!");
2032 })
2033 .ok()?
2034 } else {
2035 let fut = tokio::time::timeout(
2036 Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
2037 self.write_ticket.acquire(),
2038 );
2039
2040 match fut.await {
2041 Ok(Ok(ticket)) => ticket,
2042 Ok(Err(_)) => {
2043 error!("Failed to acquire write ticket, may be poisoned.");
2044 return None;
2045 }
2046 Err(_) => {
2047 error!("Failed to acquire write ticket, server is overloaded.");
2048 return None;
2049 }
2050 }
2051 };
2052
2053 let db_ticket = if cfg!(test) {
2057 self.db_tickets
2058 .try_acquire()
2059 .inspect_err(|err| {
2060 error!(?err, "Unable to acquire write db_ticket!");
2061 })
2062 .ok()?
2063 } else {
2064 self.db_tickets
2065 .acquire()
2066 .await
2067 .inspect_err(|err| {
2068 error!(?err, "Unable to acquire write db_ticket!");
2069 })
2070 .ok()?
2071 };
2072
2073 Some((write_ticket, db_ticket))
2074 }
2075
2076 pub async fn write(
2077 &self,
2078 curtime: Duration,
2079 ) -> Result<QueryServerWriteTransaction<'_>, OperationError> {
2080 let (write_ticket, db_ticket) = self
2081 .write_acquire_ticket()
2082 .await
2083 .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
2084
2085 let be_txn = self.be.write()?;
2090
2091 let schema_write = self.schema.write();
2092 let d_info = self.d_info.write();
2093 let system_config = self.system_config.write();
2094 let feature_config = self.feature_config.write();
2095 let phase = self.phase.write();
2096
2097 let mut cid = self.cid_max.write();
2098 *cid = Cid::new_lamport(cid.s_uuid, curtime, &cid.ts);
2100
2101 let trim_cid = cid.sub_secs(CHANGELOG_MAX_AGE)?;
2102
2103 Ok(QueryServerWriteTransaction {
2104 committed: false,
2111 phase,
2112 d_info,
2113 system_config,
2114 feature_config,
2115 curtime,
2116 cid,
2117 trim_cid,
2118 be_txn,
2119 schema: schema_write,
2120 accesscontrols: self.accesscontrols.write(),
2121 changed_flags: ChangeFlag::empty(),
2122 changed_uuid: HashSet::new(),
2123 _db_ticket: db_ticket,
2124 _write_ticket: write_ticket,
2125 resolve_filter_cache: self.resolve_filter_cache.read(),
2126 resolve_filter_cache_clear: false,
2127 resolve_filter_cache_write: self.resolve_filter_cache.write(),
2128 dyngroup_cache: self.dyngroup_cache.write(),
2129 key_providers: self.key_providers.write(),
2130 txn_name_to_uuid: Default::default(),
2131 })
2132 }
2133
2134 #[cfg(any(test, debug_assertions))]
2135 pub async fn clear_cache(&self) -> Result<(), OperationError> {
2136 let ct = duration_from_epoch_now();
2137 let mut w_txn = self.write(ct).await?;
2138 w_txn.clear_cache()?;
2139 w_txn.commit()
2140 }
2141
2142 pub async fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
2143 let current_time = duration_from_epoch_now();
2144 if self
2149 .write(current_time)
2150 .await
2151 .and_then(|mut txn| {
2152 txn.force_schema_reload();
2153 txn.commit()
2154 })
2155 .is_err()
2156 {
2157 return vec![Err(ConsistencyError::Unknown)];
2158 };
2159
2160 match self.read().await {
2161 Ok(mut r_txn) => r_txn.verify(),
2162 Err(_) => vec![Err(ConsistencyError::Unknown)],
2163 }
2164 }
2165}
2166
2167impl<'a> QueryServerWriteTransaction<'a> {
2168 pub(crate) fn get_server_uuid(&self) -> Uuid {
2169 self.cid.s_uuid
2171 }
2172
2173 pub(crate) fn reset_server_uuid(&mut self) -> Result<(), OperationError> {
2174 let s_uuid = self.be_txn.reset_db_s_uuid().map_err(|err| {
2175 error!(?err, "Failed to reset server replication uuid");
2176 err
2177 })?;
2178
2179 debug!(?s_uuid, "reset server replication uuid");
2180
2181 self.cid.s_uuid = s_uuid;
2182
2183 Ok(())
2184 }
2185
2186 pub(crate) fn get_curtime(&self) -> Duration {
2187 self.curtime
2188 }
2189
2190 pub(crate) fn get_curtime_odt(&self) -> OffsetDateTime {
2191 OffsetDateTime::UNIX_EPOCH + self.curtime
2192 }
2193
2194 pub(crate) fn get_cid(&self) -> &Cid {
2195 &self.cid
2196 }
2197
2198 pub(crate) fn get_key_providers_mut(&mut self) -> &mut KeyProvidersWriteTransaction<'a> {
2199 &mut self.key_providers
2200 }
2201
2202 pub(crate) fn get_dyngroup_cache(&mut self) -> &mut DynGroupCache {
2203 &mut self.dyngroup_cache
2204 }
2205
2206 pub fn domain_raise(&mut self, level: u32) -> Result<(), OperationError> {
2207 if level > DOMAIN_MAX_LEVEL {
2208 return Err(OperationError::MG0002RaiseDomainLevelExceedsMaximum);
2209 }
2210
2211 let modl = ModifyList::new_purge_and_set(Attribute::Version, Value::Uint32(level));
2212 let udi = PVUUID_DOMAIN_INFO.clone();
2213 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2214 self.internal_modify(&filt, &modl)
2215 }
2216
2217 pub fn domain_remigrate(&mut self, level: u32) -> Result<(), OperationError> {
2218 let mut_d_info = self.d_info.get_mut();
2219
2220 if level > mut_d_info.d_vers {
2224 return Ok(());
2226 };
2227
2228 info!(
2229 "Preparing to re-migrate from {} -> {}",
2230 level, mut_d_info.d_vers
2231 );
2232 mut_d_info.d_vers = level;
2233 self.changed_flags.insert(ChangeFlag::DOMAIN);
2234
2235 Ok(())
2236 }
2237
2238 #[instrument(level = "debug", skip_all)]
2239 pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> {
2240 if self.get_domain_version() < DOMAIN_LEVEL_1_11 {
2241 let filt = filter!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2244 let res = self.internal_search(filt).map_err(|e| {
2245 error!("reload schema internal search failed {:?}", e);
2246 e
2247 })?;
2248 let attributetypes: Result<Vec<_>, _> =
2250 res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
2251
2252 let attributetypes = attributetypes.map_err(|e| {
2253 error!("reload schema attributetypes {:?}", e);
2254 e
2255 })?;
2256
2257 self.schema
2258 .update_attributes(attributetypes.into_iter())
2259 .map_err(|e| {
2260 error!("reload schema update attributetypes {:?}", e);
2261 e
2262 })?;
2263
2264 let filt = filter!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
2266 let res = self.internal_search(filt).map_err(|e| {
2267 error!("reload schema internal search failed {:?}", e);
2268 e
2269 })?;
2270 let classtypes: Result<Vec<_>, _> =
2272 res.iter().map(|e| SchemaClass::try_from(e)).collect();
2273 let classtypes = classtypes.map_err(|e| {
2274 error!("reload schema classtypes {:?}", e);
2275 e
2276 })?;
2277
2278 self.schema
2279 .update_classes(classtypes.into_iter())
2280 .map_err(|e| {
2281 error!("reload schema update classtypes {:?}", e);
2282 e
2283 })?;
2284
2285 let valid_r = self.schema.validate();
2287
2288 if !valid_r.is_empty() {
2290 error!("Schema reload failed -> {:?}", valid_r);
2292 return Err(OperationError::ConsistencyError(
2293 valid_r.into_iter().filter_map(|v| v.err()).collect(),
2294 ));
2295 };
2296 } else {
2297 match self.get_domain_version() {
2298 DOMAIN_LEVEL_1_11 => self.migrate_schema_1_11()?,
2299 _ => {
2300 debug_assert!(false, "domain level was not configured in reload_schema");
2301 return Err(OperationError::MG0001InvalidReMigrationLevel);
2302 }
2303 }
2304 }
2305
2306 trace!("Reloading idxmeta ...");
2308 self.be_txn
2309 .update_idxmeta(self.schema.reload_idxmeta())
2310 .inspect_err(|err| {
2311 error!(?err, "reload schema update idxmeta");
2312 })?;
2313
2314 self.resolve_filter_cache_clear = true;
2317
2318 DynGroup::reload(self)?;
2321
2322 Ok(())
2323 }
2324
2325 #[instrument(level = "debug", skip_all)]
2326 fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
2327 trace!("ACP reload started ...");
2335
2336 let filt = filter!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
2339
2340 let res = self.internal_search(filt).map_err(|e| {
2341 admin_error!(
2342 err = ?e,
2343 "reload accesscontrols internal search failed",
2344 );
2345 e
2346 })?;
2347
2348 let sync_agreement_map: HashMap<Uuid, BTreeSet<Attribute>> = res
2349 .iter()
2350 .filter_map(|e| {
2351 e.get_ava_as_iutf8(Attribute::SyncYieldAuthority)
2352 .map(|set| {
2353 let set: BTreeSet<_> =
2354 set.iter().map(|s| Attribute::from(s.as_str())).collect();
2355 (e.get_uuid(), set)
2356 })
2357 })
2358 .collect();
2359
2360 self.accesscontrols
2361 .update_sync_agreements(sync_agreement_map);
2362
2363 let filt = filter!(f_and!([
2365 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2366 f_eq(Attribute::Class, EntryClass::AccessControlSearch.into()),
2367 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2368 ]));
2369
2370 let res = self.internal_search(filt).map_err(|e| {
2371 admin_error!(
2372 err = ?e,
2373 "reload accesscontrols internal search failed",
2374 );
2375 e
2376 })?;
2377 let search_acps: Result<Vec<_>, _> = res
2378 .iter()
2379 .map(|e| AccessControlSearch::try_from(self, e))
2380 .collect();
2381
2382 let search_acps = search_acps.map_err(|e| {
2383 admin_error!(err = ?e, "Unable to parse search accesscontrols");
2384 e
2385 })?;
2386
2387 self.accesscontrols
2388 .update_search(search_acps)
2389 .map_err(|e| {
2390 admin_error!(err = ?e, "Failed to update search accesscontrols");
2391 e
2392 })?;
2393 let filt = filter!(f_and!([
2395 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2396 f_eq(Attribute::Class, EntryClass::AccessControlCreate.into()),
2397 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2398 ]));
2399
2400 let res = self.internal_search(filt).map_err(|e| {
2401 admin_error!(
2402 err = ?e,
2403 "reload accesscontrols internal search failed"
2404 );
2405 e
2406 })?;
2407 let create_acps: Result<Vec<_>, _> = res
2408 .iter()
2409 .map(|e| AccessControlCreate::try_from(self, e))
2410 .collect();
2411
2412 let create_acps = create_acps.map_err(|e| {
2413 admin_error!(err = ?e, "Unable to parse create accesscontrols");
2414 e
2415 })?;
2416
2417 self.accesscontrols
2418 .update_create(create_acps)
2419 .map_err(|e| {
2420 admin_error!(err = ?e, "Failed to update create accesscontrols");
2421 e
2422 })?;
2423 let filt = filter!(f_and!([
2425 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2426 f_eq(Attribute::Class, EntryClass::AccessControlModify.into()),
2427 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2428 ]));
2429
2430 let res = self.internal_search(filt).map_err(|e| {
2431 admin_error!("reload accesscontrols internal search failed {:?}", e);
2432 e
2433 })?;
2434 let modify_acps: Result<Vec<_>, _> = res
2435 .iter()
2436 .map(|e| AccessControlModify::try_from(self, e))
2437 .collect();
2438
2439 let modify_acps = modify_acps.map_err(|e| {
2440 admin_error!("Unable to parse modify accesscontrols {:?}", e);
2441 e
2442 })?;
2443
2444 self.accesscontrols
2445 .update_modify(modify_acps)
2446 .map_err(|e| {
2447 admin_error!("Failed to update modify accesscontrols {:?}", e);
2448 e
2449 })?;
2450 let filt = filter!(f_and!([
2452 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2453 f_eq(Attribute::Class, EntryClass::AccessControlDelete.into()),
2454 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2455 ]));
2456
2457 let res = self.internal_search(filt).map_err(|e| {
2458 admin_error!("reload accesscontrols internal search failed {:?}", e);
2459 e
2460 })?;
2461 let delete_acps: Result<Vec<_>, _> = res
2462 .iter()
2463 .map(|e| AccessControlDelete::try_from(self, e))
2464 .collect();
2465
2466 let delete_acps = delete_acps.map_err(|e| {
2467 admin_error!("Unable to parse delete accesscontrols {:?}", e);
2468 e
2469 })?;
2470
2471 self.accesscontrols.update_delete(delete_acps).map_err(|e| {
2472 admin_error!("Failed to update delete accesscontrols {:?}", e);
2473 e
2474 })
2475 }
2476
2477 #[instrument(level = "debug", skip_all)]
2478 pub(crate) fn reload_key_material(&mut self) -> Result<(), OperationError> {
2479 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
2480
2481 let res = self.internal_search(filt).map_err(|e| {
2482 admin_error!(
2483 err = ?e,
2484 "reload key providers internal search failed",
2485 );
2486 e
2487 })?;
2488
2489 let providers = res
2492 .iter()
2493 .map(|e| KeyProvider::try_from(e).and_then(|kp| kp.test().map(|()| kp)))
2494 .collect::<Result<Vec<_>, _>>()?;
2495
2496 self.key_providers.update_providers(providers)?;
2497
2498 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
2499
2500 let res = self.internal_search(filt).map_err(|e| {
2501 admin_error!(
2502 err = ?e,
2503 "reload key objects internal search failed",
2504 );
2505 e
2506 })?;
2507
2508 res.iter()
2509 .try_for_each(|entry| self.key_providers.load_key_object(entry.as_ref()))
2510 }
2511
2512 #[instrument(level = "debug", skip_all)]
2513 pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
2514 let denied_names = self.get_sc_denied_names()?;
2515 let pw_badlist = self.get_sc_password_badlist()?;
2516
2517 let mut_system_config = self.system_config.get_mut();
2518 mut_system_config.denied_names = denied_names;
2519 mut_system_config.pw_badlist = pw_badlist;
2520 Ok(())
2521 }
2522
2523 #[instrument(level = "debug", skip_all)]
2525 pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
2526 let domain_info = self.internal_search_uuid(UUID_DOMAIN_INFO).map_err(|err| {
2527 error!(?err, "Error getting domain info");
2528 err
2529 })?;
2530
2531 let domain_info_version = domain_info
2532 .get_ava_single_uint32(Attribute::Version)
2533 .ok_or_else(|| {
2534 error!("domain info missing attribute version");
2535 OperationError::InvalidEntryState
2536 })?;
2537
2538 let domain_info_patch_level = domain_info
2539 .get_ava_single_uint32(Attribute::PatchLevel)
2540 .unwrap_or(0);
2541
2542 let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
2546 let domain_info_devel_taint = current_devel_flag
2547 || domain_info
2548 .get_ava_single_bool(Attribute::DomainDevelopmentTaint)
2549 .unwrap_or_default();
2550
2551 let domain_allow_easter_eggs = domain_info
2552 .get_ava_single_bool(Attribute::DomainAllowEasterEggs)
2553 .unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some());
2555
2556 let domain_allow_account_recovery = domain_info
2557 .get_ava_single_bool(Attribute::DomainAllowAccountRecovery)
2558 .unwrap_or_default();
2559
2560 let mut_d_info = self.d_info.get_mut();
2564 debug!(?mut_d_info);
2565 let previous_version = mut_d_info.d_vers;
2567 let previous_patch_level = mut_d_info.d_patch_level;
2568 mut_d_info.d_vers = domain_info_version;
2569 mut_d_info.d_patch_level = domain_info_patch_level;
2570 mut_d_info.d_devel_taint = domain_info_devel_taint;
2571 mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs;
2572 mut_d_info.d_allow_account_recovery = domain_allow_account_recovery;
2573
2574 debug!(?mut_d_info);
2575
2576 if (previous_version == domain_info_version
2579 && previous_patch_level == domain_info_patch_level)
2580 || *self.phase < ServerPhase::DomainInfoReady
2581 {
2582 return Ok(());
2583 }
2584
2585 debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2586 debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2587
2588 if previous_version == DOMAIN_LEVEL_0 {
2592 debug!(
2593 "Server was just brought up, skipping migrations as we are already at target level"
2594 );
2595 return Ok(());
2596 }
2597
2598 if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL {
2599 let valid_levels: Vec<_> =
2600 (DOMAIN_MIN_REMIGRATION_LEVEL..DOMAIN_PREVIOUS_TGT_LEVEL).collect();
2601 error!("UNABLE TO PROCEED. You have requested an initial migration level which is lower than supported.");
2602 error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
2603 error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2604 error!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2605 error!(?valid_levels);
2606
2607 debug_assert!(false);
2608
2609 return Err(OperationError::MG0001InvalidReMigrationLevel);
2610 }
2611
2612 const { assert!(DOMAIN_MIN_REMIGRATION_LEVEL <= DOMAIN_PREVIOUS_TGT_LEVEL) };
2625 const { assert!(DOMAIN_MIN_REMIGRATION_LEVEL >= DOMAIN_MIN_CREATION_LEVEL) };
2626
2627 const { assert!(DOMAIN_MIN_CREATION_LEVEL >= DOMAIN_LEVEL_10) };
2628
2629 if previous_version <= DOMAIN_LEVEL_10 && domain_info_version >= DOMAIN_LEVEL_11 {
2633 self.migrate_domain_10_to_11()?;
2635 }
2636
2637 if previous_version <= DOMAIN_LEVEL_11 && domain_info_version >= DOMAIN_LEVEL_12 {
2638 self.migrate_domain_11_to_12()?;
2640 }
2641
2642 if previous_version <= DOMAIN_LEVEL_12 && domain_info_version >= DOMAIN_LEVEL_13 {
2643 self.migrate_domain_12_to_13()?;
2645 }
2646
2647 if previous_version <= DOMAIN_LEVEL_13 && domain_info_version >= DOMAIN_LEVEL_14 {
2648 self.migrate_domain_13_to_14()?;
2650 }
2651
2652 if previous_version <= DOMAIN_LEVEL_14 && domain_info_version >= DOMAIN_LEVEL_1_11 {
2653 self.migrate_domain_1_10_to_1_11()?;
2655 }
2656
2657 if previous_version <= DOMAIN_LEVEL_1_11 && domain_info_version >= DOMAIN_LEVEL_1_12 {
2658 self.migrate_domain_1_11_to_1_12()?;
2660 }
2661
2662 const { assert!(DOMAIN_MAX_LEVEL == DOMAIN_LEVEL_1_12) };
2666 debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
2667
2668 Ok(())
2669 }
2670
2671 #[instrument(level = "debug", skip_all)]
2673 pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
2674 let domain_entry = self.get_db_domain()?;
2675
2676 let domain_name = domain_entry
2677 .get_ava_single_iname(Attribute::DomainName)
2678 .map(str::to_string)
2679 .ok_or(OperationError::InvalidEntryState)?;
2680
2681 let display_name = domain_entry
2682 .get_ava_single_utf8(Attribute::DomainDisplayName)
2683 .map(str::to_string)
2684 .unwrap_or_else(|| format!("Kanidm {domain_name}"));
2685
2686 let domain_ldap_allow_unix_pw_bind = domain_entry
2687 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
2688 .unwrap_or(true);
2689
2690 let domain_image = domain_entry.get_ava_single_image(Attribute::Image);
2691
2692 let domain_uuid = self.be_txn.get_db_d_uuid()?;
2693
2694 let mut_d_info = self.d_info.get_mut();
2695 mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind;
2696 if mut_d_info.d_uuid != domain_uuid {
2697 admin_warn!(
2698 "Using domain uuid from the database {} - was {} in memory",
2699 domain_name,
2700 mut_d_info.d_name,
2701 );
2702 mut_d_info.d_uuid = domain_uuid;
2703 }
2704 if mut_d_info.d_name != domain_name {
2705 admin_warn!(
2706 "Using domain name from the database {} - was {} in memory",
2707 domain_name,
2708 mut_d_info.d_name,
2709 );
2710 admin_warn!(
2711 "If you think this is an error, see https://kanidm.github.io/kanidm/master/domain_rename.html"
2712 );
2713 mut_d_info.d_name = domain_name;
2714 }
2715 mut_d_info.d_display = display_name;
2716 mut_d_info.d_image = domain_image;
2717 Ok(())
2718 }
2719
2720 #[instrument(level = "debug", skip_all)]
2722 pub(crate) fn reload_feature_config(&mut self) -> Result<(), OperationError> {
2723 let filt = filter!(f_eq(Attribute::Class, EntryClass::Feature.into()));
2724
2725 let feature_configs = self.internal_search(filt).inspect_err(|err| {
2726 error!(?err, "reload feature configuration internal search failed",)
2727 })?;
2728
2729 let current_time = self.get_curtime();
2730 let domain_level = self.get_domain_version();
2731
2732 let mut hmac_name_history_fixup = false;
2733
2734 for feature_entry in feature_configs {
2738 match feature_entry.get_uuid() {
2739 UUID_HMAC_NAME_FEATURE => {
2740 if domain_level < DOMAIN_LEVEL_12 {
2741 trace!("Skipping hmac name history config");
2742 continue;
2743 }
2744
2745 let key_object = self
2746 .get_key_providers()
2747 .get_key_object_handle(UUID_HMAC_NAME_FEATURE)
2748 .ok_or(OperationError::KP0079KeyObjectNotFound)?;
2749
2750 let mut key = HmacSha256Key::default();
2751 key_object.hkdf_s256_expand(
2752 UUID_HMAC_NAME_FEATURE.as_bytes(),
2753 key.as_mut_slice(),
2754 current_time,
2755 )?;
2756
2757 drop(key_object);
2758
2759 let new_feature_enabled_state = feature_entry
2760 .get_ava_single_bool(Attribute::Enabled)
2761 .unwrap_or_default();
2762
2763 let feature_config_txn = self.feature_config.get_mut();
2764
2765 hmac_name_history_fixup =
2766 !feature_config_txn.hmac_name_history.enabled && new_feature_enabled_state;
2767
2768 feature_config_txn.hmac_name_history.enabled = new_feature_enabled_state;
2769
2770 std::mem::swap(&mut key, &mut feature_config_txn.hmac_name_history.key);
2771 }
2772 feature_uuid => {
2773 error!(
2774 ?feature_uuid,
2775 "Unrecognised feature uuid, unable to proceed"
2776 );
2777 return Err(OperationError::KG004UnknownFeatureUuid);
2778 }
2779 }
2780 }
2781
2782 if hmac_name_history_fixup {
2783 plugins::hmac_name_unique::HmacNameUnique::fixup(self)?;
2784 }
2785
2786 Ok(())
2787 }
2788
2789 pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2793 let modl = ModifyList::new_purge_and_set(
2794 Attribute::DomainDisplayName,
2795 Value::new_utf8(new_domain_name.to_string()),
2796 );
2797 let udi = PVUUID_DOMAIN_INFO.clone();
2798 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2799 self.internal_modify(&filt, &modl)
2800 }
2801
2802 pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2815 let modl =
2816 ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name));
2817 let udi = PVUUID_DOMAIN_INFO.clone();
2818 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2819 self.internal_modify(&filt, &modl)
2820 }
2821
2822 pub fn reindex(&mut self, immediate: bool) -> Result<(), OperationError> {
2823 self.be_txn.reindex(immediate)
2827 }
2828
2829 fn force_schema_reload(&mut self) {
2830 self.changed_flags.insert(ChangeFlag::SCHEMA);
2831 }
2832
2833 fn force_domain_reload(&mut self) {
2834 self.changed_flags.insert(ChangeFlag::DOMAIN);
2835 }
2836
2837 pub(crate) fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> {
2838 self.be_txn.upgrade_reindex(v)
2839 }
2840
2841 #[inline]
2842 pub(crate) fn get_changed_app(&self) -> bool {
2843 self.changed_flags.contains(ChangeFlag::APPLICATION)
2844 }
2845
2846 #[inline]
2847 pub(crate) fn get_changed_oauth2(&self) -> bool {
2848 self.changed_flags.contains(ChangeFlag::OAUTH2)
2849 }
2850
2851 #[inline]
2852 pub(crate) fn clear_changed_oauth2(&mut self) {
2853 self.changed_flags.remove(ChangeFlag::OAUTH2)
2854 }
2855
2856 #[inline]
2857 pub(crate) fn get_changed_oauth2_client(&self) -> bool {
2858 self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
2859 }
2860
2861 pub(crate) fn set_phase_bootstrap(&mut self) {
2864 *self.phase = ServerPhase::Bootstrap;
2865 }
2866
2867 pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
2869 if phase > *self.phase {
2871 *self.phase = phase
2872 }
2873 }
2874
2875 pub(crate) fn get_phase(&mut self) -> ServerPhase {
2876 *self.phase
2877 }
2878
2879 pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
2880 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2883 self.reload_domain_info_version()?;
2884 }
2885
2886 if self.changed_flags.intersects(ChangeFlag::SCHEMA) {
2891 self.reload_schema()?;
2892
2893 if *self.phase >= ServerPhase::Running {
2897 self.reindex(false)?;
2898 self.reload_schema()?;
2899 }
2900 }
2901
2902 if self
2905 .changed_flags
2906 .intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
2907 {
2908 self.reload_key_material()?;
2909 }
2910
2911 if self
2919 .changed_flags
2920 .intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
2921 {
2922 self.reload_accesscontrols()?;
2923 } else {
2924 }
2929
2930 if self.changed_flags.intersects(ChangeFlag::SYSTEM_CONFIG) {
2931 self.reload_system_config()?;
2932 }
2933
2934 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2935 self.reload_domain_info()?;
2936 }
2937
2938 if self
2939 .changed_flags
2940 .intersects(ChangeFlag::FEATURE | ChangeFlag::KEY_MATERIAL)
2941 {
2942 self.reload_feature_config()?;
2943 }
2944
2945 self.changed_flags.remove(
2947 ChangeFlag::DOMAIN
2948 | ChangeFlag::SCHEMA
2949 | ChangeFlag::FEATURE
2950 | ChangeFlag::SYSTEM_CONFIG
2951 | ChangeFlag::ACP
2952 | ChangeFlag::SYNC_AGREEMENT
2953 | ChangeFlag::KEY_MATERIAL,
2954 );
2955
2956 Ok(())
2957 }
2958
2959 #[cfg(any(test, debug_assertions))]
2960 #[instrument(level = "debug", skip_all)]
2961 pub fn clear_cache(&mut self) -> Result<(), OperationError> {
2962 self.be_txn.clear_cache()
2963 }
2964
2965 #[instrument(level = "debug", name="qswt_commit" skip_all)]
2966 pub fn commit(mut self) -> Result<(), OperationError> {
2967 self.reload()?;
2968
2969 let QueryServerWriteTransaction {
2971 committed,
2972 phase,
2973 d_info,
2974 system_config,
2975 feature_config,
2976 mut be_txn,
2977 schema,
2978 accesscontrols,
2979 cid,
2980 dyngroup_cache,
2981 key_providers,
2982 _db_ticket,
2984 _write_ticket,
2985 curtime: _,
2987 trim_cid: _,
2988 changed_flags,
2989 changed_uuid: _,
2990 resolve_filter_cache: _,
2991 resolve_filter_cache_clear,
2992 mut resolve_filter_cache_write,
2993 txn_name_to_uuid: _,
2994 } = self;
2995 debug_assert!(!committed);
2996
2997 trace!(
2999 changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
3000 );
3001
3002 be_txn.set_db_ts_max(cid.ts)?;
3005 cid.commit();
3006
3007 if resolve_filter_cache_clear {
3009 resolve_filter_cache_write.clear();
3010 }
3011 resolve_filter_cache_write.commit();
3012
3013 schema
3017 .commit()
3018 .map(|_| d_info.commit())
3019 .map(|_| system_config.commit())
3020 .map(|_| feature_config.commit())
3021 .map(|_| phase.commit())
3022 .map(|_| dyngroup_cache.commit())
3023 .and_then(|_| key_providers.commit())
3024 .and_then(|_| accesscontrols.commit())
3025 .and_then(|_| be_txn.commit())
3026 }
3027
3028 pub(crate) fn get_txn_cid(&self) -> &Cid {
3029 &self.cid
3030 }
3031}
3032
3033#[cfg(test)]
3034mod tests {
3035 use crate::prelude::*;
3036 use kanidm_proto::scim_v1::{
3037 server::{ScimListResponse, ScimReference},
3038 JsonValue, ScimEntryGetQuery, ScimFilter,
3039 };
3040 use std::num::NonZeroU64;
3041
3042 #[qs_test]
3043 async fn test_name_to_uuid(server: &QueryServer) {
3044 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3045
3046 let t_uuid = Uuid::new_v4();
3047 assert!(server_txn
3048 .internal_create(vec![entry_init!(
3049 (Attribute::Class, EntryClass::Object.to_value()),
3050 (Attribute::Class, EntryClass::Account.to_value()),
3051 (Attribute::Class, EntryClass::Person.to_value()),
3052 (Attribute::Name, Value::new_iname("testperson1")),
3053 (Attribute::Uuid, Value::Uuid(t_uuid)),
3054 (Attribute::Description, Value::new_utf8s("testperson1")),
3055 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3056 ),])
3057 .is_ok());
3058
3059 let r1 = server_txn.name_to_uuid("testpers");
3061 assert!(r1.is_err());
3062 let r2 = server_txn.name_to_uuid("tEsTpErS");
3064 assert!(r2.is_err());
3065 let r3 = server_txn.name_to_uuid("testperson1");
3067 assert_eq!(r3, Ok(t_uuid));
3068 let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
3070 assert_eq!(r4, Ok(t_uuid));
3071 let r5 = server_txn.name_to_uuid("name=testperson1");
3073 assert_eq!(r5, Ok(t_uuid));
3074 let r6 = server_txn.name_to_uuid("name=testperson1,o=example");
3076 assert_eq!(r6, Ok(t_uuid));
3077 }
3078
3079 #[qs_test]
3080 async fn test_external_id_to_uuid(server: &QueryServer) {
3081 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3082
3083 let t_uuid = Uuid::new_v4();
3084 assert!(server_txn
3085 .internal_create(vec![entry_init!(
3086 (Attribute::Class, EntryClass::Object.to_value()),
3087 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3088 (Attribute::Uuid, Value::Uuid(t_uuid)),
3089 (
3090 Attribute::SyncExternalId,
3091 Value::new_iutf8("uid=testperson")
3092 )
3093 ),])
3094 .is_ok());
3095
3096 let r1 = server_txn.sync_external_id_to_uuid("tobias");
3098 assert_eq!(r1, Ok(None));
3099 let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
3101 assert_eq!(r2, Ok(None));
3102 let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
3104 assert_eq!(r3, Ok(Some(t_uuid)));
3105 let r4 = server_txn.sync_external_id_to_uuid("uId=TeStPeRsOn");
3107 assert_eq!(r4, Ok(Some(t_uuid)));
3108 }
3109
3110 #[qs_test]
3111 async fn test_uuid_to_spn(server: &QueryServer) {
3112 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3113
3114 let e1 = entry_init!(
3115 (Attribute::Class, EntryClass::Object.to_value()),
3116 (Attribute::Class, EntryClass::Person.to_value()),
3117 (Attribute::Class, EntryClass::Account.to_value()),
3118 (Attribute::Name, Value::new_iname("testperson1")),
3119 (
3120 Attribute::Uuid,
3121 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3122 ),
3123 (Attribute::Description, Value::new_utf8s("testperson1")),
3124 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3125 );
3126 let ce = CreateEvent::new_internal(vec![e1]);
3127 let cr = server_txn.create(&ce);
3128 assert!(cr.is_ok());
3129
3130 let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3132 assert_eq!(r1, Ok(None));
3134 let r3 = server_txn.uuid_to_spn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3136 println!("{r3:?}");
3137 assert_eq!(
3138 r3.unwrap().unwrap(),
3139 Value::new_spn_str("testperson1", "example.com")
3140 );
3141 let r4 = server_txn.uuid_to_spn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3143 assert_eq!(
3144 r4.unwrap().unwrap(),
3145 Value::new_spn_str("testperson1", "example.com")
3146 );
3147 }
3148
3149 #[qs_test]
3150 async fn test_uuid_to_rdn(server: &QueryServer) {
3151 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3152
3153 let e1 = entry_init!(
3154 (Attribute::Class, EntryClass::Object.to_value()),
3155 (Attribute::Class, EntryClass::Person.to_value()),
3156 (Attribute::Class, EntryClass::Account.to_value()),
3157 (Attribute::Name, Value::new_iname("testperson1")),
3158 (
3159 Attribute::Uuid,
3160 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3161 ),
3162 (Attribute::Description, Value::new_utf8s("testperson")),
3163 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3164 );
3165 let ce = CreateEvent::new_internal(vec![e1]);
3166 let cr = server_txn.create(&ce);
3167 assert!(cr.is_ok());
3168
3169 let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3171 assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
3173 let r3 = server_txn.uuid_to_rdn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3175 println!("{r3:?}");
3176 assert_eq!(r3.unwrap(), "spn=testperson1@example.com");
3177 let r4 = server_txn.uuid_to_rdn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3179 assert_eq!(r4.unwrap(), "spn=testperson1@example.com");
3180 }
3181
3182 #[qs_test]
3183 async fn test_clone_value(server: &QueryServer) {
3184 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3185 let e1 = entry_init!(
3186 (Attribute::Class, EntryClass::Object.to_value()),
3187 (Attribute::Class, EntryClass::Account.to_value()),
3188 (Attribute::Class, EntryClass::Person.to_value()),
3189 (Attribute::Name, Value::new_iname("testperson1")),
3190 (
3191 Attribute::Uuid,
3192 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3193 ),
3194 (Attribute::Description, Value::new_utf8s("testperson1")),
3195 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3196 );
3197 let ce = CreateEvent::new_internal(vec![e1]);
3198 let cr = server_txn.create(&ce);
3199 assert!(cr.is_ok());
3200
3201 let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
3203
3204 assert!(r1.is_err());
3205
3206 let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
3209
3210 assert!(r2.is_err());
3211
3212 let r3 = server_txn.clone_value(&Attribute::from("member"), "testperson1");
3214
3215 assert_eq!(
3216 r3,
3217 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3218 );
3219
3220 let r4 = server_txn.clone_value(
3222 &Attribute::from("member"),
3223 "cc8e95b4-c24f-4d68-ba54-8bed76f63930",
3224 );
3225
3226 debug!("{:?}", r4);
3227 assert_eq!(
3228 r4,
3229 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3230 );
3231 }
3232
3233 #[qs_test(domain_level=DOMAIN_LEVEL_13)]
3234 async fn test_dynamic_schema_class(server: &QueryServer) {
3235 let e1 = entry_init!(
3236 (Attribute::Class, EntryClass::Object.to_value()),
3237 (Attribute::Class, EntryClass::TestClass.to_value()),
3238 (Attribute::Name, Value::new_iname("testobj1")),
3239 (
3240 Attribute::Uuid,
3241 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3242 )
3243 );
3244
3245 let e_cd = entry_init!(
3247 (Attribute::Class, EntryClass::Object.to_value()),
3248 (Attribute::Class, EntryClass::ClassType.to_value()),
3249 (Attribute::ClassName, EntryClass::TestClass.to_value()),
3250 (
3251 Attribute::Uuid,
3252 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3253 ),
3254 (Attribute::Description, Value::new_utf8s("Test Class")),
3255 (Attribute::May, Value::from(Attribute::Name))
3256 );
3257 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3258 let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
3260 assert!(server_txn.create(&ce_class).is_ok());
3261 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3263 assert!(server_txn.create(&ce_fail).is_err());
3264
3265 server_txn.commit().expect("should not fail");
3267
3268 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3270 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3273 assert!(server_txn.create(&ce_work).is_ok());
3274
3275 server_txn.commit().expect("should not fail");
3277
3278 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3280 let de_class = DeleteEvent::new_internal_invalid(filter!(f_eq(
3282 Attribute::ClassName,
3283 EntryClass::TestClass.into()
3284 )));
3285 assert!(server_txn.delete(&de_class).is_ok());
3286 server_txn.commit().expect("should not fail");
3288
3289 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3291 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3293 assert!(server_txn.create(&ce_fail).is_err());
3294 let testobj1 = server_txn
3296 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3297 .expect("failed");
3298 assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into()));
3299
3300 server_txn.commit().expect("should not fail");
3302 }
3304
3305 #[qs_test(domain_level=DOMAIN_LEVEL_13)]
3306 async fn test_dynamic_schema_attr(server: &QueryServer) {
3307 let e1 = entry_init!(
3308 (Attribute::Class, EntryClass::Object.to_value()),
3309 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3310 (Attribute::Name, Value::new_iname("testobj1")),
3311 (
3312 Attribute::Uuid,
3313 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3314 ),
3315 (Attribute::TestAttr, Value::new_utf8s("test"))
3316 );
3317
3318 let e_ad = entry_init!(
3320 (Attribute::Class, EntryClass::Object.to_value()),
3321 (Attribute::Class, EntryClass::AttributeType.to_value()),
3322 (
3323 Attribute::Uuid,
3324 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3325 ),
3326 (Attribute::AttributeName, Value::from(Attribute::TestAttr)),
3327 (Attribute::Description, Value::new_utf8s("Test Attribute")),
3328 (Attribute::MultiValue, Value::new_bool(false)),
3329 (Attribute::Unique, Value::new_bool(false)),
3330 (
3331 Attribute::Syntax,
3332 Value::new_syntaxs("UTF8STRING").expect("syntax")
3333 )
3334 );
3335
3336 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3337 let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
3339 assert!(server_txn.create(&ce_attr).is_ok());
3340 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3342 assert!(server_txn.create(&ce_fail).is_err());
3343
3344 server_txn.commit().expect("should not fail");
3346
3347 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3349 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3352 assert!(server_txn.create(&ce_work).is_ok());
3353
3354 server_txn.commit().expect("should not fail");
3356
3357 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3359 let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
3361 Attribute::AttributeName,
3362 PartialValue::from(Attribute::TestAttr)
3363 )));
3364 assert!(server_txn.delete(&de_attr).is_ok());
3365 server_txn.commit().expect("should not fail");
3367
3368 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3370 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3372 assert!(server_txn.create(&ce_fail).is_err());
3373 let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
3375 assert!(server_txn.internal_search(filt).is_err());
3376 let testobj1 = server_txn
3379 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3380 .expect("failed");
3381 assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test")));
3382
3383 server_txn.commit().expect("should not fail");
3384 }
3386
3387 #[qs_test]
3388 async fn test_scim_entry_structure(server: &QueryServer) {
3389 let mut read_txn = server.read().await.unwrap();
3390
3391 let entry = read_txn
3393 .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
3394 .unwrap();
3395
3396 let reduced = entry.as_ref().clone().into_reduced();
3398 let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
3399
3400 assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE);
3402 let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap();
3403 match name_scim {
3404 ScimValueKanidm::String(name) => {
3405 assert_eq!(name.clone(), "idm_people_self_name_write")
3406 }
3407 _ => {
3408 panic!("expected String, actual {name_scim:?}");
3409 }
3410 }
3411
3412 let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap();
3414 match entry_managed_by_scim {
3415 ScimValueKanidm::EntryReferences(managed_by) => {
3416 assert_eq!(
3417 managed_by.first().unwrap().clone(),
3418 ScimReference {
3419 uuid: UUID_IDM_ADMINS,
3420 value: "idm_admins@example.com".to_string()
3421 }
3422 )
3423 }
3424 _ => {
3425 panic!("expected EntryReference, actual {entry_managed_by_scim:?}");
3426 }
3427 }
3428
3429 let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap();
3430 match members_scim {
3431 ScimValueKanidm::EntryReferences(members) => {
3432 assert_eq!(
3433 members.first().unwrap().clone(),
3434 ScimReference {
3435 uuid: UUID_IDM_ALL_PERSONS,
3436 value: "idm_all_persons@example.com".to_string()
3437 }
3438 )
3439 }
3440 _ => {
3441 panic!("expected EntryReferences, actual {members_scim:?}");
3442 }
3443 }
3444 }
3445
3446 #[qs_test]
3447 async fn test_scim_effective_access_query(server: &QueryServer) {
3448 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3449
3450 let group_uuid = Uuid::new_v4();
3451 let e1 = entry_init!(
3452 (Attribute::Class, EntryClass::Object.to_value()),
3453 (Attribute::Class, EntryClass::Group.to_value()),
3454 (Attribute::Name, Value::new_iname("testgroup")),
3455 (Attribute::Uuid, Value::Uuid(group_uuid))
3456 );
3457
3458 assert!(server_txn.internal_create(vec![e1]).is_ok());
3459 assert!(server_txn.commit().is_ok());
3460
3461 let mut server_txn = server.read().await.unwrap();
3464
3465 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3466 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3467
3468 let query = ScimEntryGetQuery {
3469 ext_access_check: true,
3470 ..Default::default()
3471 };
3472
3473 let scim_entry = server_txn
3474 .scim_entry_id_get_ext(group_uuid, EntryClass::Group, query, idm_admin_ident)
3475 .unwrap();
3476
3477 let ext_access_check = scim_entry.ext_access_check.unwrap();
3478
3479 trace!(?ext_access_check);
3480
3481 assert!(ext_access_check.delete);
3482 assert!(ext_access_check.search.check(&Attribute::DirectMemberOf));
3483 assert!(ext_access_check.search.check(&Attribute::MemberOf));
3484 assert!(ext_access_check.search.check(&Attribute::Name));
3485 assert!(ext_access_check.modify_present.check(&Attribute::Name));
3486 assert!(ext_access_check.modify_remove.check(&Attribute::Name));
3487 }
3488
3489 #[qs_test]
3490 async fn test_scim_basic_search_ext_query(server: &QueryServer) {
3491 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3492
3493 let group_uuid = Uuid::new_v4();
3494 let e1 = entry_init!(
3495 (Attribute::Class, EntryClass::Object.to_value()),
3496 (Attribute::Class, EntryClass::Group.to_value()),
3497 (Attribute::Name, Value::new_iname("testgroup")),
3498 (Attribute::Uuid, Value::Uuid(group_uuid))
3499 );
3500
3501 assert!(server_txn.internal_create(vec![e1]).is_ok());
3502 assert!(server_txn.commit().is_ok());
3503
3504 let mut server_txn = server.read().await.unwrap();
3506
3507 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3508 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3509
3510 let filter = ScimFilter::And(
3511 Box::new(ScimFilter::Equal(
3512 Attribute::Class.into(),
3513 EntryClass::Group.into(),
3514 )),
3515 Box::new(ScimFilter::Equal(
3516 Attribute::Uuid.into(),
3517 JsonValue::String(group_uuid.to_string()),
3518 )),
3519 );
3520
3521 let base: ScimListResponse = server_txn
3522 .scim_search_ext(idm_admin_ident, filter, ScimEntryGetQuery::default())
3523 .unwrap();
3524
3525 assert_eq!(base.resources.len(), 1);
3526 assert_eq!(base.total_results, 1);
3527 assert_eq!(base.items_per_page, None);
3529 assert_eq!(base.start_index, None);
3530 assert_eq!(base.resources[0].header.id, group_uuid);
3531 }
3532
3533 #[qs_test]
3534 async fn test_scim_basic_search_ext_query_with_sort(server: &QueryServer) {
3535 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3536
3537 for i in (1..4).rev() {
3538 let e1 = entry_init!(
3539 (Attribute::Class, EntryClass::Object.to_value()),
3540 (Attribute::Class, EntryClass::Group.to_value()),
3541 (
3542 Attribute::Name,
3543 Value::new_iname(format!("testgroup{i}").as_str())
3544 )
3545 );
3546 assert!(server_txn.internal_create(vec![e1]).is_ok());
3547 }
3548
3549 assert!(server_txn.commit().is_ok());
3550
3551 let mut server_txn = server.read().await.unwrap();
3553
3554 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3555 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3556
3557 let filter = ScimFilter::And(
3558 Box::new(ScimFilter::Equal(
3559 Attribute::Class.into(),
3560 EntryClass::Group.into(),
3561 )),
3562 Box::new(ScimFilter::StartsWith(
3563 Attribute::Name.into(),
3564 JsonValue::String("testgroup".into()),
3565 )),
3566 );
3567
3568 let base: ScimListResponse = server_txn
3569 .scim_search_ext(
3570 idm_admin_ident.clone(),
3571 filter.clone(),
3572 ScimEntryGetQuery {
3573 sort_by: Some(Attribute::Name),
3574 ..Default::default()
3575 },
3576 )
3577 .unwrap();
3578
3579 assert_eq!(base.resources.len(), 3);
3580 assert_eq!(base.total_results, 3);
3581 assert_eq!(base.items_per_page, None);
3583 assert_eq!(base.start_index, None);
3584
3585 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3586 base.resources[0].attrs.get(&Attribute::Name)
3587 else {
3588 panic!("Invalid data in attribute.");
3589 };
3590 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3591 base.resources[1].attrs.get(&Attribute::Name)
3592 else {
3593 panic!("Invalid data in attribute.");
3594 };
3595 let Some(ScimValueKanidm::String(testgroup_name_2)) =
3596 base.resources[2].attrs.get(&Attribute::Name)
3597 else {
3598 panic!("Invalid data in attribute.");
3599 };
3600
3601 assert!(testgroup_name_0 < testgroup_name_1);
3602 assert!(testgroup_name_0 < testgroup_name_2);
3603 assert!(testgroup_name_1 < testgroup_name_2);
3604
3605 let base: ScimListResponse = server_txn
3608 .scim_search_ext(
3609 idm_admin_ident.clone(),
3610 filter.clone(),
3611 ScimEntryGetQuery {
3612 count: NonZeroU64::new(1),
3613 ..Default::default()
3614 },
3615 )
3616 .unwrap();
3617
3618 assert_eq!(base.resources.len(), 1);
3619 assert_eq!(base.total_results, 3);
3620 assert_eq!(base.items_per_page, NonZeroU64::new(1));
3622 assert_eq!(base.start_index, NonZeroU64::new(1));
3623
3624 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3625 base.resources[0].attrs.get(&Attribute::Name)
3626 else {
3627 panic!("Invalid data in attribute.");
3628 };
3629 assert_eq!(testgroup_name_0, "testgroup3");
3631
3632 let base: ScimListResponse = server_txn
3635 .scim_search_ext(
3636 idm_admin_ident,
3637 filter.clone(),
3638 ScimEntryGetQuery {
3639 sort_by: Some(Attribute::Name),
3640 count: NonZeroU64::new(2),
3641 start_index: NonZeroU64::new(2),
3642 ..Default::default()
3643 },
3644 )
3645 .unwrap();
3646
3647 assert_eq!(base.resources.len(), 2);
3648 assert_eq!(base.total_results, 3);
3649 assert_eq!(base.items_per_page, NonZeroU64::new(2));
3650 assert_eq!(base.start_index, NonZeroU64::new(2));
3651
3652 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3653 base.resources[0].attrs.get(&Attribute::Name)
3654 else {
3655 panic!("Invalid data in attribute.");
3656 };
3657 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3658 base.resources[1].attrs.get(&Attribute::Name)
3659 else {
3660 panic!("Invalid data in attribute.");
3661 };
3662 assert_eq!(testgroup_name_0, "testgroup2");
3664 assert_eq!(testgroup_name_1, "testgroup3");
3665 }
3666}