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 let filt = filter!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2243 let res = self.internal_search(filt).map_err(|e| {
2244 admin_error!("reload schema internal search failed {:?}", e);
2245 e
2246 })?;
2247 let attributetypes: Result<Vec<_>, _> =
2249 res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
2250
2251 let attributetypes = attributetypes.map_err(|e| {
2252 admin_error!("reload schema attributetypes {:?}", e);
2253 e
2254 })?;
2255
2256 self.schema.update_attributes(attributetypes).map_err(|e| {
2257 admin_error!("reload schema update attributetypes {:?}", e);
2258 e
2259 })?;
2260
2261 let filt = filter!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
2263 let res = self.internal_search(filt).map_err(|e| {
2264 admin_error!("reload schema internal search failed {:?}", e);
2265 e
2266 })?;
2267 let classtypes: Result<Vec<_>, _> = res.iter().map(|e| SchemaClass::try_from(e)).collect();
2269 let classtypes = classtypes.map_err(|e| {
2270 admin_error!("reload schema classtypes {:?}", e);
2271 e
2272 })?;
2273
2274 self.schema.update_classes(classtypes).map_err(|e| {
2275 admin_error!("reload schema update classtypes {:?}", e);
2276 e
2277 })?;
2278
2279 let valid_r = self.schema.validate();
2281
2282 if valid_r.is_empty() {
2284 trace!("Reloading idxmeta ...");
2286 self.be_txn
2287 .update_idxmeta(self.schema.reload_idxmeta())
2288 .map_err(|e| {
2289 admin_error!("reload schema update idxmeta {:?}", e);
2290 e
2291 })
2292 } else {
2293 admin_error!("Schema reload failed -> {:?}", valid_r);
2295 Err(OperationError::ConsistencyError(
2296 valid_r.into_iter().filter_map(|v| v.err()).collect(),
2297 ))
2298 }?;
2299
2300 self.resolve_filter_cache_clear = true;
2303
2304 DynGroup::reload(self)?;
2307
2308 Ok(())
2309 }
2310
2311 #[instrument(level = "debug", skip_all)]
2312 fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
2313 trace!("ACP reload started ...");
2321
2322 let filt = filter!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
2325
2326 let res = self.internal_search(filt).map_err(|e| {
2327 admin_error!(
2328 err = ?e,
2329 "reload accesscontrols internal search failed",
2330 );
2331 e
2332 })?;
2333
2334 let sync_agreement_map: HashMap<Uuid, BTreeSet<Attribute>> = res
2335 .iter()
2336 .filter_map(|e| {
2337 e.get_ava_as_iutf8(Attribute::SyncYieldAuthority)
2338 .map(|set| {
2339 let set: BTreeSet<_> =
2340 set.iter().map(|s| Attribute::from(s.as_str())).collect();
2341 (e.get_uuid(), set)
2342 })
2343 })
2344 .collect();
2345
2346 self.accesscontrols
2347 .update_sync_agreements(sync_agreement_map);
2348
2349 let filt = filter!(f_and!([
2351 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2352 f_eq(Attribute::Class, EntryClass::AccessControlSearch.into()),
2353 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2354 ]));
2355
2356 let res = self.internal_search(filt).map_err(|e| {
2357 admin_error!(
2358 err = ?e,
2359 "reload accesscontrols internal search failed",
2360 );
2361 e
2362 })?;
2363 let search_acps: Result<Vec<_>, _> = res
2364 .iter()
2365 .map(|e| AccessControlSearch::try_from(self, e))
2366 .collect();
2367
2368 let search_acps = search_acps.map_err(|e| {
2369 admin_error!(err = ?e, "Unable to parse search accesscontrols");
2370 e
2371 })?;
2372
2373 self.accesscontrols
2374 .update_search(search_acps)
2375 .map_err(|e| {
2376 admin_error!(err = ?e, "Failed to update search accesscontrols");
2377 e
2378 })?;
2379 let filt = filter!(f_and!([
2381 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2382 f_eq(Attribute::Class, EntryClass::AccessControlCreate.into()),
2383 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2384 ]));
2385
2386 let res = self.internal_search(filt).map_err(|e| {
2387 admin_error!(
2388 err = ?e,
2389 "reload accesscontrols internal search failed"
2390 );
2391 e
2392 })?;
2393 let create_acps: Result<Vec<_>, _> = res
2394 .iter()
2395 .map(|e| AccessControlCreate::try_from(self, e))
2396 .collect();
2397
2398 let create_acps = create_acps.map_err(|e| {
2399 admin_error!(err = ?e, "Unable to parse create accesscontrols");
2400 e
2401 })?;
2402
2403 self.accesscontrols
2404 .update_create(create_acps)
2405 .map_err(|e| {
2406 admin_error!(err = ?e, "Failed to update create accesscontrols");
2407 e
2408 })?;
2409 let filt = filter!(f_and!([
2411 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2412 f_eq(Attribute::Class, EntryClass::AccessControlModify.into()),
2413 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2414 ]));
2415
2416 let res = self.internal_search(filt).map_err(|e| {
2417 admin_error!("reload accesscontrols internal search failed {:?}", e);
2418 e
2419 })?;
2420 let modify_acps: Result<Vec<_>, _> = res
2421 .iter()
2422 .map(|e| AccessControlModify::try_from(self, e))
2423 .collect();
2424
2425 let modify_acps = modify_acps.map_err(|e| {
2426 admin_error!("Unable to parse modify accesscontrols {:?}", e);
2427 e
2428 })?;
2429
2430 self.accesscontrols
2431 .update_modify(modify_acps)
2432 .map_err(|e| {
2433 admin_error!("Failed to update modify accesscontrols {:?}", e);
2434 e
2435 })?;
2436 let filt = filter!(f_and!([
2438 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2439 f_eq(Attribute::Class, EntryClass::AccessControlDelete.into()),
2440 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2441 ]));
2442
2443 let res = self.internal_search(filt).map_err(|e| {
2444 admin_error!("reload accesscontrols internal search failed {:?}", e);
2445 e
2446 })?;
2447 let delete_acps: Result<Vec<_>, _> = res
2448 .iter()
2449 .map(|e| AccessControlDelete::try_from(self, e))
2450 .collect();
2451
2452 let delete_acps = delete_acps.map_err(|e| {
2453 admin_error!("Unable to parse delete accesscontrols {:?}", e);
2454 e
2455 })?;
2456
2457 self.accesscontrols.update_delete(delete_acps).map_err(|e| {
2458 admin_error!("Failed to update delete accesscontrols {:?}", e);
2459 e
2460 })
2461 }
2462
2463 #[instrument(level = "debug", skip_all)]
2464 pub(crate) fn reload_key_material(&mut self) -> Result<(), OperationError> {
2465 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
2466
2467 let res = self.internal_search(filt).map_err(|e| {
2468 admin_error!(
2469 err = ?e,
2470 "reload key providers internal search failed",
2471 );
2472 e
2473 })?;
2474
2475 let providers = res
2478 .iter()
2479 .map(|e| KeyProvider::try_from(e).and_then(|kp| kp.test().map(|()| kp)))
2480 .collect::<Result<Vec<_>, _>>()?;
2481
2482 self.key_providers.update_providers(providers)?;
2483
2484 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
2485
2486 let res = self.internal_search(filt).map_err(|e| {
2487 admin_error!(
2488 err = ?e,
2489 "reload key objects internal search failed",
2490 );
2491 e
2492 })?;
2493
2494 res.iter()
2495 .try_for_each(|entry| self.key_providers.load_key_object(entry.as_ref()))
2496 }
2497
2498 #[instrument(level = "debug", skip_all)]
2499 pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
2500 let denied_names = self.get_sc_denied_names()?;
2501 let pw_badlist = self.get_sc_password_badlist()?;
2502
2503 let mut_system_config = self.system_config.get_mut();
2504 mut_system_config.denied_names = denied_names;
2505 mut_system_config.pw_badlist = pw_badlist;
2506 Ok(())
2507 }
2508
2509 #[instrument(level = "debug", skip_all)]
2511 pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
2512 let domain_info = self.internal_search_uuid(UUID_DOMAIN_INFO).map_err(|err| {
2513 error!(?err, "Error getting domain info");
2514 err
2515 })?;
2516
2517 let domain_info_version = domain_info
2518 .get_ava_single_uint32(Attribute::Version)
2519 .ok_or_else(|| {
2520 error!("domain info missing attribute version");
2521 OperationError::InvalidEntryState
2522 })?;
2523
2524 let domain_info_patch_level = domain_info
2525 .get_ava_single_uint32(Attribute::PatchLevel)
2526 .unwrap_or(0);
2527
2528 let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
2532 let domain_info_devel_taint = current_devel_flag
2533 || domain_info
2534 .get_ava_single_bool(Attribute::DomainDevelopmentTaint)
2535 .unwrap_or_default();
2536
2537 let domain_allow_easter_eggs = domain_info
2538 .get_ava_single_bool(Attribute::DomainAllowEasterEggs)
2539 .unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some());
2541
2542 let domain_allow_account_recovery = domain_info
2543 .get_ava_single_bool(Attribute::DomainAllowAccountRecovery)
2544 .unwrap_or_default();
2545
2546 let mut_d_info = self.d_info.get_mut();
2550 debug!(?mut_d_info);
2551 let previous_version = mut_d_info.d_vers;
2553 let previous_patch_level = mut_d_info.d_patch_level;
2554 mut_d_info.d_vers = domain_info_version;
2555 mut_d_info.d_patch_level = domain_info_patch_level;
2556 mut_d_info.d_devel_taint = domain_info_devel_taint;
2557 mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs;
2558 mut_d_info.d_allow_account_recovery = domain_allow_account_recovery;
2559
2560 debug!(?mut_d_info);
2561
2562 if (previous_version == domain_info_version
2565 && previous_patch_level == domain_info_patch_level)
2566 || *self.phase < ServerPhase::DomainInfoReady
2567 {
2568 return Ok(());
2569 }
2570
2571 debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2572 debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2573
2574 if previous_version == DOMAIN_LEVEL_0 {
2578 debug!(
2579 "Server was just brought up, skipping migrations as we are already at target level"
2580 );
2581 return Ok(());
2582 }
2583
2584 if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL {
2585 let valid_levels: Vec<_> =
2586 (DOMAIN_MIN_REMIGRATION_LEVEL..DOMAIN_PREVIOUS_TGT_LEVEL).collect();
2587 error!("UNABLE TO PROCEED. You have requested an initial migration level which is lower than supported.");
2588 error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
2589 error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2590 error!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2591 error!(?valid_levels);
2592
2593 debug_assert!(false);
2594
2595 return Err(OperationError::MG0001InvalidReMigrationLevel);
2596 }
2597
2598 const { assert!(DOMAIN_MIN_REMIGRATION_LEVEL <= DOMAIN_PREVIOUS_TGT_LEVEL) };
2611 const { assert!(DOMAIN_MIN_REMIGRATION_LEVEL >= DOMAIN_MIN_CREATION_LEVEL) };
2612
2613 const { assert!(DOMAIN_MIN_CREATION_LEVEL >= DOMAIN_LEVEL_10) };
2614
2615 if previous_version <= DOMAIN_LEVEL_10 && domain_info_version >= DOMAIN_LEVEL_11 {
2619 self.migrate_domain_10_to_11()?;
2621 }
2622
2623 if previous_version <= DOMAIN_LEVEL_11 && domain_info_version >= DOMAIN_LEVEL_12 {
2624 self.migrate_domain_11_to_12()?;
2626 }
2627
2628 if previous_version <= DOMAIN_LEVEL_12 && domain_info_version >= DOMAIN_LEVEL_13 {
2629 self.migrate_domain_12_to_13()?;
2631 }
2632
2633 if previous_version <= DOMAIN_LEVEL_13 && domain_info_version >= DOMAIN_LEVEL_14 {
2634 self.migrate_domain_13_to_14()?;
2636 }
2637
2638 if previous_version <= DOMAIN_LEVEL_14 && domain_info_version >= DOMAIN_LEVEL_15 {
2639 self.migrate_domain_14_to_15()?;
2641 }
2642
2643 const { assert!(DOMAIN_MAX_LEVEL == DOMAIN_LEVEL_15) };
2647 debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
2648
2649 Ok(())
2650 }
2651
2652 #[instrument(level = "debug", skip_all)]
2654 pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
2655 let domain_entry = self.get_db_domain()?;
2656
2657 let domain_name = domain_entry
2658 .get_ava_single_iname(Attribute::DomainName)
2659 .map(str::to_string)
2660 .ok_or(OperationError::InvalidEntryState)?;
2661
2662 let display_name = domain_entry
2663 .get_ava_single_utf8(Attribute::DomainDisplayName)
2664 .map(str::to_string)
2665 .unwrap_or_else(|| format!("Kanidm {domain_name}"));
2666
2667 let domain_ldap_allow_unix_pw_bind = domain_entry
2668 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
2669 .unwrap_or(true);
2670
2671 let domain_image = domain_entry.get_ava_single_image(Attribute::Image);
2672
2673 let domain_uuid = self.be_txn.get_db_d_uuid()?;
2674
2675 let mut_d_info = self.d_info.get_mut();
2676 mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind;
2677 if mut_d_info.d_uuid != domain_uuid {
2678 admin_warn!(
2679 "Using domain uuid from the database {} - was {} in memory",
2680 domain_name,
2681 mut_d_info.d_name,
2682 );
2683 mut_d_info.d_uuid = domain_uuid;
2684 }
2685 if mut_d_info.d_name != domain_name {
2686 admin_warn!(
2687 "Using domain name from the database {} - was {} in memory",
2688 domain_name,
2689 mut_d_info.d_name,
2690 );
2691 admin_warn!(
2692 "If you think this is an error, see https://kanidm.github.io/kanidm/master/domain_rename.html"
2693 );
2694 mut_d_info.d_name = domain_name;
2695 }
2696 mut_d_info.d_display = display_name;
2697 mut_d_info.d_image = domain_image;
2698 Ok(())
2699 }
2700
2701 #[instrument(level = "debug", skip_all)]
2703 pub(crate) fn reload_feature_config(&mut self) -> Result<(), OperationError> {
2704 let filt = filter!(f_eq(Attribute::Class, EntryClass::Feature.into()));
2705
2706 let feature_configs = self.internal_search(filt).inspect_err(|err| {
2707 error!(?err, "reload feature configuration internal search failed",)
2708 })?;
2709
2710 let current_time = self.get_curtime();
2711 let domain_level = self.get_domain_version();
2712
2713 let mut hmac_name_history_fixup = false;
2714
2715 for feature_entry in feature_configs {
2719 match feature_entry.get_uuid() {
2720 UUID_HMAC_NAME_FEATURE => {
2721 if domain_level < DOMAIN_LEVEL_12 {
2722 trace!("Skipping hmac name history config");
2723 continue;
2724 }
2725
2726 let key_object = self
2727 .get_key_providers()
2728 .get_key_object_handle(UUID_HMAC_NAME_FEATURE)
2729 .ok_or(OperationError::KP0079KeyObjectNotFound)?;
2730
2731 let mut key = HmacSha256Key::default();
2732 key_object.hkdf_s256_expand(
2733 UUID_HMAC_NAME_FEATURE.as_bytes(),
2734 key.as_mut_slice(),
2735 current_time,
2736 )?;
2737
2738 drop(key_object);
2739
2740 let new_feature_enabled_state = feature_entry
2741 .get_ava_single_bool(Attribute::Enabled)
2742 .unwrap_or_default();
2743
2744 let feature_config_txn = self.feature_config.get_mut();
2745
2746 hmac_name_history_fixup =
2747 !feature_config_txn.hmac_name_history.enabled && new_feature_enabled_state;
2748
2749 feature_config_txn.hmac_name_history.enabled = new_feature_enabled_state;
2750
2751 std::mem::swap(&mut key, &mut feature_config_txn.hmac_name_history.key);
2752 }
2753 feature_uuid => {
2754 error!(
2755 ?feature_uuid,
2756 "Unrecognised feature uuid, unable to proceed"
2757 );
2758 return Err(OperationError::KG004UnknownFeatureUuid);
2759 }
2760 }
2761 }
2762
2763 if hmac_name_history_fixup {
2764 plugins::hmac_name_unique::HmacNameUnique::fixup(self)?;
2765 }
2766
2767 Ok(())
2768 }
2769
2770 pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2774 let modl = ModifyList::new_purge_and_set(
2775 Attribute::DomainDisplayName,
2776 Value::new_utf8(new_domain_name.to_string()),
2777 );
2778 let udi = PVUUID_DOMAIN_INFO.clone();
2779 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2780 self.internal_modify(&filt, &modl)
2781 }
2782
2783 pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2796 let modl =
2797 ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name));
2798 let udi = PVUUID_DOMAIN_INFO.clone();
2799 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2800 self.internal_modify(&filt, &modl)
2801 }
2802
2803 pub fn reindex(&mut self, immediate: bool) -> Result<(), OperationError> {
2804 self.be_txn.reindex(immediate)
2808 }
2809
2810 fn force_schema_reload(&mut self) {
2811 self.changed_flags.insert(ChangeFlag::SCHEMA);
2812 }
2813
2814 fn force_domain_reload(&mut self) {
2815 self.changed_flags.insert(ChangeFlag::DOMAIN);
2816 }
2817
2818 pub(crate) fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> {
2819 self.be_txn.upgrade_reindex(v)
2820 }
2821
2822 #[inline]
2823 pub(crate) fn get_changed_app(&self) -> bool {
2824 self.changed_flags.contains(ChangeFlag::APPLICATION)
2825 }
2826
2827 #[inline]
2828 pub(crate) fn get_changed_oauth2(&self) -> bool {
2829 self.changed_flags.contains(ChangeFlag::OAUTH2)
2830 }
2831
2832 #[inline]
2833 pub(crate) fn clear_changed_oauth2(&mut self) {
2834 self.changed_flags.remove(ChangeFlag::OAUTH2)
2835 }
2836
2837 #[inline]
2838 pub(crate) fn get_changed_oauth2_client(&self) -> bool {
2839 self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
2840 }
2841
2842 pub(crate) fn set_phase_bootstrap(&mut self) {
2845 *self.phase = ServerPhase::Bootstrap;
2846 }
2847
2848 pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
2850 if phase > *self.phase {
2852 *self.phase = phase
2853 }
2854 }
2855
2856 pub(crate) fn get_phase(&mut self) -> ServerPhase {
2857 *self.phase
2858 }
2859
2860 pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
2861 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2864 self.reload_domain_info_version()?;
2865 }
2866
2867 if self.changed_flags.intersects(ChangeFlag::SCHEMA) {
2872 self.reload_schema()?;
2873
2874 if *self.phase >= ServerPhase::Running {
2878 self.reindex(false)?;
2879 self.reload_schema()?;
2880 }
2881 }
2882
2883 if self
2886 .changed_flags
2887 .intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
2888 {
2889 self.reload_key_material()?;
2890 }
2891
2892 if self
2900 .changed_flags
2901 .intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
2902 {
2903 self.reload_accesscontrols()?;
2904 } else {
2905 }
2910
2911 if self.changed_flags.intersects(ChangeFlag::SYSTEM_CONFIG) {
2912 self.reload_system_config()?;
2913 }
2914
2915 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2916 self.reload_domain_info()?;
2917 }
2918
2919 if self
2920 .changed_flags
2921 .intersects(ChangeFlag::FEATURE | ChangeFlag::KEY_MATERIAL)
2922 {
2923 self.reload_feature_config()?;
2924 }
2925
2926 self.changed_flags.remove(
2928 ChangeFlag::DOMAIN
2929 | ChangeFlag::SCHEMA
2930 | ChangeFlag::FEATURE
2931 | ChangeFlag::SYSTEM_CONFIG
2932 | ChangeFlag::ACP
2933 | ChangeFlag::SYNC_AGREEMENT
2934 | ChangeFlag::KEY_MATERIAL,
2935 );
2936
2937 Ok(())
2938 }
2939
2940 #[cfg(any(test, debug_assertions))]
2941 #[instrument(level = "debug", skip_all)]
2942 pub fn clear_cache(&mut self) -> Result<(), OperationError> {
2943 self.be_txn.clear_cache()
2944 }
2945
2946 #[instrument(level = "debug", name="qswt_commit" skip_all)]
2947 pub fn commit(mut self) -> Result<(), OperationError> {
2948 self.reload()?;
2949
2950 let QueryServerWriteTransaction {
2952 committed,
2953 phase,
2954 d_info,
2955 system_config,
2956 feature_config,
2957 mut be_txn,
2958 schema,
2959 accesscontrols,
2960 cid,
2961 dyngroup_cache,
2962 key_providers,
2963 _db_ticket,
2965 _write_ticket,
2966 curtime: _,
2968 trim_cid: _,
2969 changed_flags,
2970 changed_uuid: _,
2971 resolve_filter_cache: _,
2972 resolve_filter_cache_clear,
2973 mut resolve_filter_cache_write,
2974 txn_name_to_uuid: _,
2975 } = self;
2976 debug_assert!(!committed);
2977
2978 trace!(
2980 changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
2981 );
2982
2983 be_txn.set_db_ts_max(cid.ts)?;
2986 cid.commit();
2987
2988 if resolve_filter_cache_clear {
2990 resolve_filter_cache_write.clear();
2991 }
2992 resolve_filter_cache_write.commit();
2993
2994 schema
2998 .commit()
2999 .map(|_| d_info.commit())
3000 .map(|_| system_config.commit())
3001 .map(|_| feature_config.commit())
3002 .map(|_| phase.commit())
3003 .map(|_| dyngroup_cache.commit())
3004 .and_then(|_| key_providers.commit())
3005 .and_then(|_| accesscontrols.commit())
3006 .and_then(|_| be_txn.commit())
3007 }
3008
3009 pub(crate) fn get_txn_cid(&self) -> &Cid {
3010 &self.cid
3011 }
3012}
3013
3014#[cfg(test)]
3015mod tests {
3016 use crate::prelude::*;
3017 use kanidm_proto::scim_v1::{
3018 server::{ScimListResponse, ScimReference},
3019 JsonValue, ScimEntryGetQuery, ScimFilter,
3020 };
3021 use std::num::NonZeroU64;
3022
3023 #[qs_test]
3024 async fn test_name_to_uuid(server: &QueryServer) {
3025 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3026
3027 let t_uuid = Uuid::new_v4();
3028 assert!(server_txn
3029 .internal_create(vec![entry_init!(
3030 (Attribute::Class, EntryClass::Object.to_value()),
3031 (Attribute::Class, EntryClass::Account.to_value()),
3032 (Attribute::Class, EntryClass::Person.to_value()),
3033 (Attribute::Name, Value::new_iname("testperson1")),
3034 (Attribute::Uuid, Value::Uuid(t_uuid)),
3035 (Attribute::Description, Value::new_utf8s("testperson1")),
3036 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3037 ),])
3038 .is_ok());
3039
3040 let r1 = server_txn.name_to_uuid("testpers");
3042 assert!(r1.is_err());
3043 let r2 = server_txn.name_to_uuid("tEsTpErS");
3045 assert!(r2.is_err());
3046 let r3 = server_txn.name_to_uuid("testperson1");
3048 assert_eq!(r3, Ok(t_uuid));
3049 let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
3051 assert_eq!(r4, Ok(t_uuid));
3052 let r5 = server_txn.name_to_uuid("name=testperson1");
3054 assert_eq!(r5, Ok(t_uuid));
3055 let r6 = server_txn.name_to_uuid("name=testperson1,o=example");
3057 assert_eq!(r6, Ok(t_uuid));
3058 }
3059
3060 #[qs_test]
3061 async fn test_external_id_to_uuid(server: &QueryServer) {
3062 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3063
3064 let t_uuid = Uuid::new_v4();
3065 assert!(server_txn
3066 .internal_create(vec![entry_init!(
3067 (Attribute::Class, EntryClass::Object.to_value()),
3068 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3069 (Attribute::Uuid, Value::Uuid(t_uuid)),
3070 (
3071 Attribute::SyncExternalId,
3072 Value::new_iutf8("uid=testperson")
3073 )
3074 ),])
3075 .is_ok());
3076
3077 let r1 = server_txn.sync_external_id_to_uuid("tobias");
3079 assert_eq!(r1, Ok(None));
3080 let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
3082 assert_eq!(r2, Ok(None));
3083 let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
3085 assert_eq!(r3, Ok(Some(t_uuid)));
3086 let r4 = server_txn.sync_external_id_to_uuid("uId=TeStPeRsOn");
3088 assert_eq!(r4, Ok(Some(t_uuid)));
3089 }
3090
3091 #[qs_test]
3092 async fn test_uuid_to_spn(server: &QueryServer) {
3093 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3094
3095 let e1 = entry_init!(
3096 (Attribute::Class, EntryClass::Object.to_value()),
3097 (Attribute::Class, EntryClass::Person.to_value()),
3098 (Attribute::Class, EntryClass::Account.to_value()),
3099 (Attribute::Name, Value::new_iname("testperson1")),
3100 (
3101 Attribute::Uuid,
3102 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3103 ),
3104 (Attribute::Description, Value::new_utf8s("testperson1")),
3105 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3106 );
3107 let ce = CreateEvent::new_internal(vec![e1]);
3108 let cr = server_txn.create(&ce);
3109 assert!(cr.is_ok());
3110
3111 let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3113 assert_eq!(r1, Ok(None));
3115 let r3 = server_txn.uuid_to_spn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3117 println!("{r3:?}");
3118 assert_eq!(
3119 r3.unwrap().unwrap(),
3120 Value::new_spn_str("testperson1", "example.com")
3121 );
3122 let r4 = server_txn.uuid_to_spn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3124 assert_eq!(
3125 r4.unwrap().unwrap(),
3126 Value::new_spn_str("testperson1", "example.com")
3127 );
3128 }
3129
3130 #[qs_test]
3131 async fn test_uuid_to_rdn(server: &QueryServer) {
3132 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3133
3134 let e1 = entry_init!(
3135 (Attribute::Class, EntryClass::Object.to_value()),
3136 (Attribute::Class, EntryClass::Person.to_value()),
3137 (Attribute::Class, EntryClass::Account.to_value()),
3138 (Attribute::Name, Value::new_iname("testperson1")),
3139 (
3140 Attribute::Uuid,
3141 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3142 ),
3143 (Attribute::Description, Value::new_utf8s("testperson")),
3144 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3145 );
3146 let ce = CreateEvent::new_internal(vec![e1]);
3147 let cr = server_txn.create(&ce);
3148 assert!(cr.is_ok());
3149
3150 let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3152 assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
3154 let r3 = server_txn.uuid_to_rdn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3156 println!("{r3:?}");
3157 assert_eq!(r3.unwrap(), "spn=testperson1@example.com");
3158 let r4 = server_txn.uuid_to_rdn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3160 assert_eq!(r4.unwrap(), "spn=testperson1@example.com");
3161 }
3162
3163 #[qs_test]
3164 async fn test_clone_value(server: &QueryServer) {
3165 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3166 let e1 = entry_init!(
3167 (Attribute::Class, EntryClass::Object.to_value()),
3168 (Attribute::Class, EntryClass::Account.to_value()),
3169 (Attribute::Class, EntryClass::Person.to_value()),
3170 (Attribute::Name, Value::new_iname("testperson1")),
3171 (
3172 Attribute::Uuid,
3173 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3174 ),
3175 (Attribute::Description, Value::new_utf8s("testperson1")),
3176 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3177 );
3178 let ce = CreateEvent::new_internal(vec![e1]);
3179 let cr = server_txn.create(&ce);
3180 assert!(cr.is_ok());
3181
3182 let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
3184
3185 assert!(r1.is_err());
3186
3187 let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
3190
3191 assert!(r2.is_err());
3192
3193 let r3 = server_txn.clone_value(&Attribute::from("member"), "testperson1");
3195
3196 assert_eq!(
3197 r3,
3198 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3199 );
3200
3201 let r4 = server_txn.clone_value(
3203 &Attribute::from("member"),
3204 "cc8e95b4-c24f-4d68-ba54-8bed76f63930",
3205 );
3206
3207 debug!("{:?}", r4);
3208 assert_eq!(
3209 r4,
3210 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3211 );
3212 }
3213
3214 #[qs_test]
3215 async fn test_dynamic_schema_class(server: &QueryServer) {
3216 let e1 = entry_init!(
3217 (Attribute::Class, EntryClass::Object.to_value()),
3218 (Attribute::Class, EntryClass::TestClass.to_value()),
3219 (Attribute::Name, Value::new_iname("testobj1")),
3220 (
3221 Attribute::Uuid,
3222 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3223 )
3224 );
3225
3226 let e_cd = entry_init!(
3228 (Attribute::Class, EntryClass::Object.to_value()),
3229 (Attribute::Class, EntryClass::ClassType.to_value()),
3230 (Attribute::ClassName, EntryClass::TestClass.to_value()),
3231 (
3232 Attribute::Uuid,
3233 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3234 ),
3235 (Attribute::Description, Value::new_utf8s("Test Class")),
3236 (Attribute::May, Value::from(Attribute::Name))
3237 );
3238 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3239 let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
3241 assert!(server_txn.create(&ce_class).is_ok());
3242 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3244 assert!(server_txn.create(&ce_fail).is_err());
3245
3246 server_txn.commit().expect("should not fail");
3248
3249 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3251 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3254 assert!(server_txn.create(&ce_work).is_ok());
3255
3256 server_txn.commit().expect("should not fail");
3258
3259 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3261 let de_class = DeleteEvent::new_internal_invalid(filter!(f_eq(
3263 Attribute::ClassName,
3264 EntryClass::TestClass.into()
3265 )));
3266 assert!(server_txn.delete(&de_class).is_ok());
3267 server_txn.commit().expect("should not fail");
3269
3270 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3272 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3274 assert!(server_txn.create(&ce_fail).is_err());
3275 let testobj1 = server_txn
3277 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3278 .expect("failed");
3279 assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into()));
3280
3281 server_txn.commit().expect("should not fail");
3283 }
3285
3286 #[qs_test]
3287 async fn test_dynamic_schema_attr(server: &QueryServer) {
3288 let e1 = entry_init!(
3289 (Attribute::Class, EntryClass::Object.to_value()),
3290 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3291 (Attribute::Name, Value::new_iname("testobj1")),
3292 (
3293 Attribute::Uuid,
3294 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3295 ),
3296 (Attribute::TestAttr, Value::new_utf8s("test"))
3297 );
3298
3299 let e_ad = entry_init!(
3301 (Attribute::Class, EntryClass::Object.to_value()),
3302 (Attribute::Class, EntryClass::AttributeType.to_value()),
3303 (
3304 Attribute::Uuid,
3305 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3306 ),
3307 (Attribute::AttributeName, Value::from(Attribute::TestAttr)),
3308 (Attribute::Description, Value::new_utf8s("Test Attribute")),
3309 (Attribute::MultiValue, Value::new_bool(false)),
3310 (Attribute::Unique, Value::new_bool(false)),
3311 (
3312 Attribute::Syntax,
3313 Value::new_syntaxs("UTF8STRING").expect("syntax")
3314 )
3315 );
3316
3317 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3318 let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
3320 assert!(server_txn.create(&ce_attr).is_ok());
3321 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3323 assert!(server_txn.create(&ce_fail).is_err());
3324
3325 server_txn.commit().expect("should not fail");
3327
3328 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3330 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3333 assert!(server_txn.create(&ce_work).is_ok());
3334
3335 server_txn.commit().expect("should not fail");
3337
3338 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3340 let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
3342 Attribute::AttributeName,
3343 PartialValue::from(Attribute::TestAttr)
3344 )));
3345 assert!(server_txn.delete(&de_attr).is_ok());
3346 server_txn.commit().expect("should not fail");
3348
3349 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3351 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3353 assert!(server_txn.create(&ce_fail).is_err());
3354 let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
3356 assert!(server_txn.internal_search(filt).is_err());
3357 let testobj1 = server_txn
3360 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3361 .expect("failed");
3362 assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test")));
3363
3364 server_txn.commit().expect("should not fail");
3365 }
3367
3368 #[qs_test]
3369 async fn test_scim_entry_structure(server: &QueryServer) {
3370 let mut read_txn = server.read().await.unwrap();
3371
3372 let entry = read_txn
3374 .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
3375 .unwrap();
3376
3377 let reduced = entry.as_ref().clone().into_reduced();
3379 let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
3380
3381 assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE);
3383 let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap();
3384 match name_scim {
3385 ScimValueKanidm::String(name) => {
3386 assert_eq!(name.clone(), "idm_people_self_name_write")
3387 }
3388 _ => {
3389 panic!("expected String, actual {name_scim:?}");
3390 }
3391 }
3392
3393 let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap();
3395 match entry_managed_by_scim {
3396 ScimValueKanidm::EntryReferences(managed_by) => {
3397 assert_eq!(
3398 managed_by.first().unwrap().clone(),
3399 ScimReference {
3400 uuid: UUID_IDM_ADMINS,
3401 value: "idm_admins@example.com".to_string()
3402 }
3403 )
3404 }
3405 _ => {
3406 panic!("expected EntryReference, actual {entry_managed_by_scim:?}");
3407 }
3408 }
3409
3410 let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap();
3411 match members_scim {
3412 ScimValueKanidm::EntryReferences(members) => {
3413 assert_eq!(
3414 members.first().unwrap().clone(),
3415 ScimReference {
3416 uuid: UUID_IDM_ALL_PERSONS,
3417 value: "idm_all_persons@example.com".to_string()
3418 }
3419 )
3420 }
3421 _ => {
3422 panic!("expected EntryReferences, actual {members_scim:?}");
3423 }
3424 }
3425 }
3426
3427 #[qs_test]
3428 async fn test_scim_effective_access_query(server: &QueryServer) {
3429 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3430
3431 let group_uuid = Uuid::new_v4();
3432 let e1 = entry_init!(
3433 (Attribute::Class, EntryClass::Object.to_value()),
3434 (Attribute::Class, EntryClass::Group.to_value()),
3435 (Attribute::Name, Value::new_iname("testgroup")),
3436 (Attribute::Uuid, Value::Uuid(group_uuid))
3437 );
3438
3439 assert!(server_txn.internal_create(vec![e1]).is_ok());
3440 assert!(server_txn.commit().is_ok());
3441
3442 let mut server_txn = server.read().await.unwrap();
3445
3446 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3447 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3448
3449 let query = ScimEntryGetQuery {
3450 ext_access_check: true,
3451 ..Default::default()
3452 };
3453
3454 let scim_entry = server_txn
3455 .scim_entry_id_get_ext(group_uuid, EntryClass::Group, query, idm_admin_ident)
3456 .unwrap();
3457
3458 let ext_access_check = scim_entry.ext_access_check.unwrap();
3459
3460 trace!(?ext_access_check);
3461
3462 assert!(ext_access_check.delete);
3463 assert!(ext_access_check.search.check(&Attribute::DirectMemberOf));
3464 assert!(ext_access_check.search.check(&Attribute::MemberOf));
3465 assert!(ext_access_check.search.check(&Attribute::Name));
3466 assert!(ext_access_check.modify_present.check(&Attribute::Name));
3467 assert!(ext_access_check.modify_remove.check(&Attribute::Name));
3468 }
3469
3470 #[qs_test]
3471 async fn test_scim_basic_search_ext_query(server: &QueryServer) {
3472 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3473
3474 let group_uuid = Uuid::new_v4();
3475 let e1 = entry_init!(
3476 (Attribute::Class, EntryClass::Object.to_value()),
3477 (Attribute::Class, EntryClass::Group.to_value()),
3478 (Attribute::Name, Value::new_iname("testgroup")),
3479 (Attribute::Uuid, Value::Uuid(group_uuid))
3480 );
3481
3482 assert!(server_txn.internal_create(vec![e1]).is_ok());
3483 assert!(server_txn.commit().is_ok());
3484
3485 let mut server_txn = server.read().await.unwrap();
3487
3488 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3489 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3490
3491 let filter = ScimFilter::And(
3492 Box::new(ScimFilter::Equal(
3493 Attribute::Class.into(),
3494 EntryClass::Group.into(),
3495 )),
3496 Box::new(ScimFilter::Equal(
3497 Attribute::Uuid.into(),
3498 JsonValue::String(group_uuid.to_string()),
3499 )),
3500 );
3501
3502 let base: ScimListResponse = server_txn
3503 .scim_search_ext(idm_admin_ident, filter, ScimEntryGetQuery::default())
3504 .unwrap();
3505
3506 assert_eq!(base.resources.len(), 1);
3507 assert_eq!(base.total_results, 1);
3508 assert_eq!(base.items_per_page, None);
3510 assert_eq!(base.start_index, None);
3511 assert_eq!(base.resources[0].header.id, group_uuid);
3512 }
3513
3514 #[qs_test]
3515 async fn test_scim_basic_search_ext_query_with_sort(server: &QueryServer) {
3516 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3517
3518 for i in (1..4).rev() {
3519 let e1 = entry_init!(
3520 (Attribute::Class, EntryClass::Object.to_value()),
3521 (Attribute::Class, EntryClass::Group.to_value()),
3522 (
3523 Attribute::Name,
3524 Value::new_iname(format!("testgroup{i}").as_str())
3525 )
3526 );
3527 assert!(server_txn.internal_create(vec![e1]).is_ok());
3528 }
3529
3530 assert!(server_txn.commit().is_ok());
3531
3532 let mut server_txn = server.read().await.unwrap();
3534
3535 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3536 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3537
3538 let filter = ScimFilter::And(
3539 Box::new(ScimFilter::Equal(
3540 Attribute::Class.into(),
3541 EntryClass::Group.into(),
3542 )),
3543 Box::new(ScimFilter::StartsWith(
3544 Attribute::Name.into(),
3545 JsonValue::String("testgroup".into()),
3546 )),
3547 );
3548
3549 let base: ScimListResponse = server_txn
3550 .scim_search_ext(
3551 idm_admin_ident.clone(),
3552 filter.clone(),
3553 ScimEntryGetQuery {
3554 sort_by: Some(Attribute::Name),
3555 ..Default::default()
3556 },
3557 )
3558 .unwrap();
3559
3560 assert_eq!(base.resources.len(), 3);
3561 assert_eq!(base.total_results, 3);
3562 assert_eq!(base.items_per_page, None);
3564 assert_eq!(base.start_index, None);
3565
3566 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3567 base.resources[0].attrs.get(&Attribute::Name)
3568 else {
3569 panic!("Invalid data in attribute.");
3570 };
3571 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3572 base.resources[1].attrs.get(&Attribute::Name)
3573 else {
3574 panic!("Invalid data in attribute.");
3575 };
3576 let Some(ScimValueKanidm::String(testgroup_name_2)) =
3577 base.resources[2].attrs.get(&Attribute::Name)
3578 else {
3579 panic!("Invalid data in attribute.");
3580 };
3581
3582 assert!(testgroup_name_0 < testgroup_name_1);
3583 assert!(testgroup_name_0 < testgroup_name_2);
3584 assert!(testgroup_name_1 < testgroup_name_2);
3585
3586 let base: ScimListResponse = server_txn
3589 .scim_search_ext(
3590 idm_admin_ident.clone(),
3591 filter.clone(),
3592 ScimEntryGetQuery {
3593 count: NonZeroU64::new(1),
3594 ..Default::default()
3595 },
3596 )
3597 .unwrap();
3598
3599 assert_eq!(base.resources.len(), 1);
3600 assert_eq!(base.total_results, 3);
3601 assert_eq!(base.items_per_page, NonZeroU64::new(1));
3603 assert_eq!(base.start_index, NonZeroU64::new(1));
3604
3605 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3606 base.resources[0].attrs.get(&Attribute::Name)
3607 else {
3608 panic!("Invalid data in attribute.");
3609 };
3610 assert_eq!(testgroup_name_0, "testgroup3");
3612
3613 let base: ScimListResponse = server_txn
3616 .scim_search_ext(
3617 idm_admin_ident,
3618 filter.clone(),
3619 ScimEntryGetQuery {
3620 sort_by: Some(Attribute::Name),
3621 count: NonZeroU64::new(2),
3622 start_index: NonZeroU64::new(2),
3623 ..Default::default()
3624 },
3625 )
3626 .unwrap();
3627
3628 assert_eq!(base.resources.len(), 2);
3629 assert_eq!(base.total_results, 3);
3630 assert_eq!(base.items_per_page, NonZeroU64::new(2));
3631 assert_eq!(base.start_index, NonZeroU64::new(2));
3632
3633 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3634 base.resources[0].attrs.get(&Attribute::Name)
3635 else {
3636 panic!("Invalid data in attribute.");
3637 };
3638 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3639 base.resources[1].attrs.get(&Attribute::Name)
3640 else {
3641 panic!("Invalid data in attribute.");
3642 };
3643 assert_eq!(testgroup_name_0, "testgroup2");
3645 assert_eq!(testgroup_name_1, "testgroup3");
3646 }
3647}