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