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