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