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::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
729 SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
730 .ok_or_else(|| OperationError::InvalidAttribute("Invalid NsUniqueId syntax".to_string())),
731 SyntaxType::DateTime => Value::new_datetime_s(value)
732 .ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
733 SyntaxType::EmailAddress => Value::new_email_address_s(value)
734 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Email Address syntax".to_string())),
735 SyntaxType::Url => Value::new_url_s(value)
736 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Url (whatwg/url) syntax".to_string())),
737 SyntaxType::OauthScope => Value::new_oauthscope(value)
738 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Oauth Scope syntax".to_string())),
739 SyntaxType::WebauthnAttestationCaList => Value::new_webauthn_attestation_ca_list(value)
740 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Webauthn Attestation CA List".to_string())),
741 SyntaxType::OauthScopeMap => Err(OperationError::InvalidAttribute("Oauth Scope Maps can not be supplied through modification - please use the IDM api".to_string())),
742 SyntaxType::OauthClaimMap => Err(OperationError::InvalidAttribute("Oauth Claim Maps can not be supplied through modification - please use the IDM api".to_string())),
743 SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute("Private Binary Values can not be supplied through modification".to_string())),
744 SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
745 SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
746 SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute("AttestedPasskey Values can not be supplied through modification".to_string())),
747 SyntaxType::Session => Err(OperationError::InvalidAttribute("Session Values can not be supplied through modification".to_string())),
748 SyntaxType::ApiToken => Err(OperationError::InvalidAttribute("ApiToken Values can not be supplied through modification".to_string())),
749 SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())),
750 SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())),
751 SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute("Oauth2Session Values can not be supplied through modification".to_string())),
752 SyntaxType::UiHint => UiHint::from_str(value)
753 .map(Value::UiHint)
754 .map_err(|()| OperationError::InvalidAttribute("Invalid uihint syntax".to_string())),
755 SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute("TotpSecret Values can not be supplied through modification".to_string())),
756 SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute("Audit logs are generated and not able to be set.".to_string())),
757 SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute("Ec keys are generated and not able to be set.".to_string())),
758 SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute("Internal keys are generated and not able to be set.".to_string())),
759 SyntaxType::HexString => Value::new_hex_string_s(value)
760 .ok_or_else(|| OperationError::InvalidAttribute("Invalid hex string syntax".to_string())),
761 SyntaxType::Certificate => Value::new_certificate_s(value)
762 .ok_or_else(|| OperationError::InvalidAttribute("Invalid x509 certificate syntax".to_string())),
763 SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute("ApplicationPassword values can not be supplied through modification".to_string())),
764 SyntaxType::Json => Err(OperationError::InvalidAttribute("Json values can not be supplied through modification".to_string())),
765 SyntaxType::Sha256 => Err(OperationError::InvalidAttribute("SHA256 values can not be supplied through modification".to_string())),
766 SyntaxType::Message => Err(OperationError::InvalidAttribute("Message values can not be supplied through modification".to_string())),
767 }
768 }
769 None => {
770 Err(OperationError::InvalidAttributeName(attr.to_string()))
773 }
774 }
775 }
776
777 fn clone_partialvalue(
778 &mut self,
779 attr: &Attribute,
780 value: &str,
781 ) -> Result<PartialValue, OperationError> {
782 let schema = self.get_schema();
783
784 match schema.get_attributes().get(attr) {
786 Some(schema_a) => {
787 match schema_a.syntax {
788 SyntaxType::Utf8String | SyntaxType::TotpSecret => {
789 Ok(PartialValue::new_utf8(value.to_string()))
790 }
791 SyntaxType::Utf8StringInsensitive
792 | SyntaxType::JwsKeyEs256
793 | SyntaxType::JwsKeyRs256 => Ok(PartialValue::new_iutf8(value)),
794 SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
795 SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
796 OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
797 }),
798 SyntaxType::SyntaxId => PartialValue::new_syntaxs(value).ok_or_else(|| {
799 OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
800 }),
801 SyntaxType::IndexId => PartialValue::new_indexes(value).ok_or_else(|| {
802 OperationError::InvalidAttribute("Invalid Index syntax".to_string())
803 }),
804 SyntaxType::CredentialType => CredentialType::try_from(value)
805 .map(PartialValue::CredentialType)
806 .map_err(|()| {
807 OperationError::InvalidAttribute(
808 "Invalid credentialtype syntax".to_string(),
809 )
810 }),
811 SyntaxType::Uuid => {
812 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
813 Ok(PartialValue::Uuid(un))
814 }
815 SyntaxType::ReferenceUuid
819 | SyntaxType::OauthScopeMap
820 | SyntaxType::Session
821 | SyntaxType::ApiToken
822 | SyntaxType::Oauth2Session
823 | SyntaxType::ApplicationPassword => {
824 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
825 Ok(PartialValue::Refer(un))
826 }
827 SyntaxType::OauthClaimMap => self
828 .name_to_uuid(value)
829 .map(PartialValue::Refer)
830 .or_else(|_| Ok(PartialValue::new_iutf8(value))),
831
832 SyntaxType::JsonFilter => {
833 PartialValue::new_json_filter_s(value).ok_or_else(|| {
834 OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
835 })
836 }
837 SyntaxType::Credential => Ok(PartialValue::new_credential_tag(value)),
838 SyntaxType::SecretUtf8String => Ok(PartialValue::new_secret_str()),
839 SyntaxType::SshKey => Ok(PartialValue::new_sshkey_tag_s(value)),
840 SyntaxType::SecurityPrincipalName => {
841 PartialValue::new_spn_s(value).ok_or_else(|| {
842 OperationError::InvalidAttribute("Invalid spn syntax".to_string())
843 })
844 }
845 SyntaxType::Uint32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
846 OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
847 }),
848 SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
849 OperationError::InvalidAttribute("Invalid cid syntax".to_string())
850 }),
851 SyntaxType::NsUniqueId => Ok(PartialValue::new_nsuniqueid_s(value)),
852 SyntaxType::DateTime => PartialValue::new_datetime_s(value).ok_or_else(|| {
853 OperationError::InvalidAttribute(
854 "Invalid DateTime (rfc3339) syntax".to_string(),
855 )
856 }),
857 SyntaxType::EmailAddress => Ok(PartialValue::new_email_address_s(value)),
858 SyntaxType::Url => PartialValue::new_url_s(value).ok_or_else(|| {
859 OperationError::InvalidAttribute(
860 "Invalid Url (whatwg/url) syntax".to_string(),
861 )
862 }),
863 SyntaxType::OauthScope => Ok(PartialValue::new_oauthscope(value)),
864 SyntaxType::PrivateBinary => Ok(PartialValue::PrivateBinary),
865 SyntaxType::IntentToken => PartialValue::new_intenttoken_s(value.to_string())
866 .ok_or_else(|| {
867 OperationError::InvalidAttribute(
868 "Invalid Intent Token ID (uuid) syntax".to_string(),
869 )
870 }),
871 SyntaxType::Passkey => PartialValue::new_passkey_s(value).ok_or_else(|| {
872 OperationError::InvalidAttribute("Invalid Passkey UUID syntax".to_string())
873 }),
874 SyntaxType::AttestedPasskey => PartialValue::new_attested_passkey_s(value)
875 .ok_or_else(|| {
876 OperationError::InvalidAttribute(
877 "Invalid AttestedPasskey UUID syntax".to_string(),
878 )
879 }),
880 SyntaxType::UiHint => UiHint::from_str(value)
881 .map(PartialValue::UiHint)
882 .map_err(|()| {
883 OperationError::InvalidAttribute("Invalid uihint syntax".to_string())
884 }),
885 SyntaxType::AuditLogString => Ok(PartialValue::new_utf8s(value)),
886 SyntaxType::EcKeyPrivate => Ok(PartialValue::SecretValue),
887 SyntaxType::Image => Ok(PartialValue::new_utf8s(value)),
888 SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
889 "Invalid - unable to query attestation CA list".to_string(),
890 )),
891 SyntaxType::HexString | SyntaxType::KeyInternal | SyntaxType::Certificate => {
892 PartialValue::new_hex_string_s(value).ok_or_else(|| {
893 OperationError::InvalidAttribute(
894 "Invalid syntax, expected hex string".to_string(),
895 )
896 })
897 }
898 SyntaxType::Sha256 => {
899 let mut sha256bytes = Sha256Output::default();
900 if hex::decode_to_slice(value, &mut sha256bytes).is_ok() {
901 Ok(PartialValue::Sha256(sha256bytes))
902 } else {
903 Err(OperationError::InvalidAttribute(
904 "Invalid syntax, expected sha256 hex string containing 64 characters".to_string(),
905 ))
906 }
907 }
908 SyntaxType::Json => Err(OperationError::InvalidAttribute(
909 "Json values can not be validated by this interface".to_string(),
910 )),
911 SyntaxType::Message => Err(OperationError::InvalidAttribute(
912 "Message values can not be validated by this interface".to_string(),
913 )),
914 }
915 }
916 None => {
917 Err(OperationError::InvalidAttributeName(attr.to_string()))
920 }
921 }
922 }
923
924 fn resolve_scim_interim(
925 &mut self,
926 scim_value_intermediate: ScimValueIntermediate,
927 ) -> Result<Option<ScimValueKanidm>, OperationError> {
928 match scim_value_intermediate {
929 ScimValueIntermediate::References(uuids) => {
930 let scim_references = uuids
931 .into_iter()
932 .map(|uuid| {
933 self.uuid_to_spn(uuid)
934 .and_then(|maybe_value| {
935 maybe_value.ok_or(OperationError::InvalidValueState)
936 })
937 .map(|value| ScimReference {
938 uuid,
939 value: value.to_proto_string_clone(),
940 })
941 })
942 .collect::<Result<Vec<_>, _>>()?;
943 Ok(Some(ScimValueKanidm::EntryReferences(scim_references)))
944 }
945 ScimValueIntermediate::Oauth2ClaimMap(unresolved_maps) => {
946 let scim_claim_maps = unresolved_maps
947 .into_iter()
948 .map(
949 |UnresolvedScimValueOauth2ClaimMap {
950 group_uuid,
951 claim,
952 join_char,
953 values,
954 }| {
955 self.uuid_to_spn(group_uuid)
956 .and_then(|maybe_value| {
957 maybe_value.ok_or(OperationError::InvalidValueState)
958 })
959 .map(|value| ScimOAuth2ClaimMap {
960 group: value.to_proto_string_clone(),
961 group_uuid,
962 claim,
963 join_char,
964 values,
965 })
966 },
967 )
968 .collect::<Result<Vec<_>, _>>()?;
969
970 Ok(Some(ScimValueKanidm::OAuth2ClaimMap(scim_claim_maps)))
971 }
972
973 ScimValueIntermediate::Oauth2ScopeMap(unresolved_maps) => {
974 let scim_claim_maps = unresolved_maps
975 .into_iter()
976 .map(|UnresolvedScimValueOauth2ScopeMap { group_uuid, scopes }| {
977 self.uuid_to_spn(group_uuid)
978 .and_then(|maybe_value| {
979 maybe_value.ok_or(OperationError::InvalidValueState)
980 })
981 .map(|value| ScimOAuth2ScopeMap {
982 group: value.to_proto_string_clone(),
983 group_uuid,
984 scopes,
985 })
986 })
987 .collect::<Result<Vec<_>, _>>()?;
988
989 Ok(Some(ScimValueKanidm::OAuth2ScopeMap(scim_claim_maps)))
990 }
991 }
992 }
993
994 fn resolve_scim_json_get(
995 &mut self,
996 attr: &Attribute,
997 value: &JsonValue,
998 ) -> Result<PartialValue, OperationError> {
999 let schema = self.get_schema();
1000 let Some(schema_a) = schema.get_attributes().get(attr) else {
1002 return Err(OperationError::InvalidAttributeName(attr.to_string()));
1005 };
1006
1007 debug!(schema_syntax = ?schema_a.syntax, ?value);
1008
1009 match schema_a.syntax {
1010 SyntaxType::Utf8String => {
1011 let JsonValue::String(value) = value else {
1012 return Err(OperationError::InvalidAttribute(attr.to_string()));
1013 };
1014 Ok(PartialValue::Utf8(value.to_string()))
1015 }
1016 SyntaxType::Utf8StringInsensitive => {
1017 let JsonValue::String(value) = value else {
1018 return Err(OperationError::InvalidAttribute(attr.to_string()));
1019 };
1020 Ok(PartialValue::new_iutf8(value))
1021 }
1022 SyntaxType::Utf8StringIname => {
1023 let JsonValue::String(value) = value else {
1024 return Err(OperationError::InvalidAttribute(attr.to_string()));
1025 };
1026 Ok(PartialValue::new_iname(value))
1027 }
1028 SyntaxType::Uuid => {
1029 let JsonValue::String(value) = value else {
1030 return Err(OperationError::InvalidAttribute(attr.to_string()));
1031 };
1032
1033 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1034 Ok(PartialValue::Uuid(un))
1035 }
1036 SyntaxType::Boolean => {
1037 let JsonValue::Bool(value) = value else {
1038 return Err(OperationError::InvalidAttribute(attr.to_string()));
1039 };
1040 Ok(PartialValue::Bool(*value))
1041 }
1042 SyntaxType::SyntaxId => {
1043 let JsonValue::String(value) = value else {
1044 return Err(OperationError::InvalidAttribute(attr.to_string()));
1045 };
1046 let Ok(value) = SyntaxType::try_from(value.as_str()) else {
1047 return Err(OperationError::InvalidAttribute(attr.to_string()));
1048 };
1049 Ok(PartialValue::Syntax(value))
1050 }
1051 SyntaxType::ReferenceUuid
1052 | SyntaxType::OauthScopeMap
1053 | SyntaxType::Session
1054 | SyntaxType::ApiToken
1055 | SyntaxType::Oauth2Session
1056 | SyntaxType::ApplicationPassword => {
1057 let JsonValue::String(value) = value else {
1058 return Err(OperationError::InvalidAttribute(attr.to_string()));
1059 };
1060
1061 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1062 Ok(PartialValue::Refer(un))
1063 }
1064
1065 _ => Err(OperationError::InvalidAttribute(attr.to_string())),
1066 }
1067 }
1068
1069 fn resolve_valueset_intermediate(
1070 &mut self,
1071 vs_inter: ValueSetIntermediate,
1072 ) -> Result<ValueSet, OperationError> {
1073 match vs_inter {
1074 ValueSetIntermediate::References {
1075 mut resolved,
1076 unresolved,
1077 } => {
1078 for value in unresolved {
1079 let un = self.name_to_uuid(value.as_str()).unwrap_or_else(|_| {
1080 warn!(
1081 ?value,
1082 "Value can not be resolved to a uuid - assuming it does not exist."
1083 );
1084 UUID_DOES_NOT_EXIST
1085 });
1086
1087 resolved.insert(un);
1088 }
1089
1090 let vs = ValueSetRefer::from_set(resolved);
1091 Ok(vs)
1092 }
1093
1094 ValueSetIntermediate::Oauth2ClaimMap {
1095 mut resolved,
1096 unresolved,
1097 } => {
1098 resolved.extend(unresolved.into_iter().map(
1099 |UnresolvedValueSetOauth2ClaimMap {
1100 group_name,
1101 claim,
1102 join_char,
1103 claim_values,
1104 }| {
1105 let group_uuid =
1106 self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1107 warn!(
1108 ?group_name,
1109 "Value can not be resolved to a uuid - assuming it does not exist."
1110 );
1111 UUID_DOES_NOT_EXIST
1112 });
1113
1114 ResolvedValueSetOauth2ClaimMap {
1115 group_uuid,
1116 claim,
1117 join_char,
1118 claim_values,
1119 }
1120 },
1121 ));
1122
1123 let vs = ValueSetOauthClaimMap::from_set(resolved);
1124 Ok(vs)
1125 }
1126
1127 ValueSetIntermediate::Oauth2ScopeMap {
1128 mut resolved,
1129 unresolved,
1130 } => {
1131 resolved.extend(unresolved.into_iter().map(
1132 |UnresolvedValueSetOauth2ScopeMap { group_name, scopes }| {
1133 let group_uuid =
1134 self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1135 warn!(
1136 ?group_name,
1137 "Value can not be resolved to a uuid - assuming it does not exist."
1138 );
1139 UUID_DOES_NOT_EXIST
1140 });
1141
1142 ResolvedValueSetOauth2ScopeMap { group_uuid, scopes }
1143 },
1144 ));
1145
1146 let vs = ValueSetOauthScopeMap::from_set(resolved);
1147 Ok(vs)
1148 }
1149 }
1150 }
1151
1152 fn resolve_valueset(&mut self, value: &ValueSet) -> Result<Vec<String>, OperationError> {
1154 if let Some(r_set) = value.as_refer_set() {
1155 let v: Result<Vec<_>, _> = r_set
1156 .iter()
1157 .copied()
1158 .map(|ur| {
1159 let nv = self.uuid_to_spn(ur)?;
1160 match nv {
1161 Some(v) => Ok(v.to_proto_string_clone()),
1162 None => Ok(uuid_to_proto_string(ur)),
1163 }
1164 })
1165 .collect();
1166 v
1167 } else if let Some(r_map) = value.as_oauthscopemap() {
1168 let v: Result<Vec<_>, _> = r_map
1169 .iter()
1170 .map(|(u, m)| {
1171 let nv = self.uuid_to_spn(*u)?;
1172 let u = match nv {
1173 Some(v) => v.to_proto_string_clone(),
1174 None => uuid_to_proto_string(*u),
1175 };
1176 Ok(format!("{u}: {m:?}"))
1177 })
1178 .collect();
1179 v
1180 } else if let Some(r_map) = value.as_oauthclaim_map() {
1181 let mut v = Vec::with_capacity(0);
1182 for (claim_name, mapping) in r_map.iter() {
1183 for (group_ref, claims) in mapping.values() {
1184 let join_char = mapping.join().to_str();
1185
1186 let nv = self.uuid_to_spn(*group_ref)?;
1187 let resolved_id = match nv {
1188 Some(v) => v.to_proto_string_clone(),
1189 None => uuid_to_proto_string(*group_ref),
1190 };
1191
1192 let joined = str_concat!(claims, ",");
1193
1194 v.push(format!("{claim_name}:{resolved_id}:{join_char}:{joined:?}"))
1195 }
1196 }
1197 Ok(v)
1198 } else {
1199 let v: Vec<_> = value.to_proto_string_clone_iter().collect();
1200 Ok(v)
1201 }
1202 }
1203
1204 fn resolve_valueset_ldap(
1205 &mut self,
1206 value: &ValueSet,
1207 basedn: &str,
1208 ) -> Result<Vec<Vec<u8>>, OperationError> {
1209 if let Some(r_set) = value.as_refer_set() {
1210 let v: Result<Vec<_>, _> = r_set
1211 .iter()
1212 .copied()
1213 .map(|ur| {
1214 let rdn = self.uuid_to_rdn(ur)?;
1215 Ok(format!("{rdn},{basedn}").into_bytes())
1216 })
1217 .collect();
1218 v
1219 } else if let Some(key_iter) = value.as_sshpubkey_string_iter() {
1222 let v: Vec<_> = key_iter.map(|s| s.into_bytes()).collect();
1223 Ok(v)
1224 } else {
1225 let v: Vec<_> = value
1226 .to_proto_string_clone_iter()
1227 .map(|s| s.into_bytes())
1228 .collect();
1229 Ok(v)
1230 }
1231 }
1232
1233 fn get_db_domain(&mut self) -> Result<Arc<EntrySealedCommitted>, OperationError> {
1234 self.internal_search_uuid(UUID_DOMAIN_INFO)
1235 }
1236
1237 fn get_domain_key_object_handle(&self) -> Result<Arc<KeyObject>, OperationError> {
1238 self.get_key_providers()
1239 .get_key_object_handle(UUID_DOMAIN_INFO)
1240 .ok_or(OperationError::KP0031KeyObjectNotFound)
1241 }
1242
1243 fn get_domain_es256_private_key(&mut self) -> Result<Vec<u8>, OperationError> {
1244 self.internal_search_uuid(UUID_DOMAIN_INFO)
1245 .and_then(|e| {
1246 e.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
1247 .map(|s| s.to_vec())
1248 .ok_or(OperationError::InvalidEntryState)
1249 })
1250 .map_err(|e| {
1251 admin_error!(?e, "Error getting domain es256 key");
1252 e
1253 })
1254 }
1255
1256 fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
1257 self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| {
1258 entry
1259 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
1260 .unwrap_or(true)
1261 })
1262 }
1263
1264 fn get_sc_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
1267 self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1268 .map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) {
1269 Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1270 None => HashSet::default(),
1271 })
1272 .map_err(|e| {
1273 error!(
1274 ?e,
1275 "Failed to retrieve password badlist from system configuration"
1276 );
1277 e
1278 })
1279 }
1280
1281 fn get_sc_denied_names(&mut self) -> Result<HashSet<String>, OperationError> {
1284 self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1285 .map(|e| match e.get_ava_iter_iname(Attribute::DeniedName) {
1286 Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1287 None => HashSet::default(),
1288 })
1289 .map_err(|e| {
1290 error!(
1291 ?e,
1292 "Failed to retrieve denied names from system configuration"
1293 );
1294 e
1295 })
1296 }
1297
1298 fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1299 self.internal_search(filter!(f_eq(
1300 Attribute::Class,
1301 EntryClass::OAuth2ResourceServer.into(),
1302 )))
1303 }
1304
1305 fn get_applications_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1306 self.internal_search(filter!(f_eq(
1307 Attribute::Class,
1308 EntryClass::Application.into(),
1309 )))
1310 }
1311
1312 #[instrument(level = "debug", skip_all)]
1313 fn consumer_get_state(&mut self) -> Result<ReplRuvRange, OperationError> {
1314 let domain_uuid = self.get_domain_uuid();
1339
1340 let ruv_snapshot = self.get_be_txn().get_ruv();
1343
1344 ruv_snapshot
1346 .current_ruv_range()
1347 .map(|ranges| ReplRuvRange::V1 {
1348 domain_uuid,
1349 ranges,
1350 })
1351 }
1352}
1353
1354impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
1358 type AccessControlsTransactionType = AccessControlsReadTransaction<'a>;
1359 type BackendTransactionType = BackendReadTransaction<'a>;
1360 type SchemaTransactionType = SchemaReadTransaction;
1361 type KeyProvidersTransactionType = KeyProvidersReadTransaction;
1362
1363 fn get_be_txn(&mut self) -> &mut BackendReadTransaction<'a> {
1364 &mut self.be_txn
1365 }
1366
1367 fn get_schema<'b>(&self) -> &'b SchemaReadTransaction {
1368 unsafe {
1372 let s = (&self.schema) as *const _;
1373 &*s
1374 }
1375 }
1376
1377 fn get_accesscontrols(&self) -> &AccessControlsReadTransaction<'a> {
1378 &self.accesscontrols
1379 }
1380
1381 fn get_key_providers(&self) -> &KeyProvidersReadTransaction {
1382 &self.key_providers
1383 }
1384
1385 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1386 Some(&mut self.resolve_filter_cache)
1387 }
1388
1389 fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig {
1390 &self.feature_config.hmac_name_history
1391 }
1392
1393 fn get_resolve_filter_cache_and_be_txn(
1394 &mut self,
1395 ) -> (
1396 &mut BackendReadTransaction<'a>,
1397 Option<&mut ResolveFilterCacheReadTxn<'a>>,
1398 ) {
1399 (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1400 }
1401
1402 fn pw_badlist(&self) -> &HashSet<String> {
1403 &self.system_config.pw_badlist
1404 }
1405
1406 fn denied_names(&self) -> &HashSet<String> {
1407 &self.system_config.denied_names
1408 }
1409
1410 fn get_domain_version(&self) -> DomainVersion {
1411 self.d_info.d_vers
1412 }
1413
1414 fn get_domain_patch_level(&self) -> u32 {
1415 self.d_info.d_patch_level
1416 }
1417
1418 fn get_domain_development_taint(&self) -> bool {
1419 self.d_info.d_devel_taint
1420 }
1421
1422 fn get_domain_uuid(&self) -> Uuid {
1423 self.d_info.d_uuid
1424 }
1425
1426 fn get_domain_name(&self) -> &str {
1427 &self.d_info.d_name
1428 }
1429
1430 fn get_domain_display_name(&self) -> &str {
1431 &self.d_info.d_display
1432 }
1433
1434 fn get_domain_image_value(&self) -> Option<ImageValue> {
1435 self.d_info.d_image.clone()
1436 }
1437}
1438
1439impl QueryServerReadTransaction<'_> {
1440 pub(crate) fn trim_cid(&self) -> &Cid {
1441 &self.trim_cid
1442 }
1443
1444 pub fn domain_info(&mut self) -> Result<ProtoDomainInfo, OperationError> {
1446 let d_info = &self.d_info;
1447
1448 Ok(ProtoDomainInfo {
1449 name: d_info.d_name.clone(),
1450 displayname: d_info.d_display.clone(),
1451 uuid: d_info.d_uuid,
1452 level: d_info.d_vers,
1453 })
1454 }
1455
1456 pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
1460 let be_errs = self.get_be_txn().verify();
1464
1465 if !be_errs.is_empty() {
1466 return be_errs;
1467 }
1468
1469 let sc_errs = self.get_schema().validate();
1471
1472 if !sc_errs.is_empty() {
1473 return sc_errs;
1474 }
1475
1476 let idx_errs = self.get_be_txn().verify_indexes();
1480
1481 if !idx_errs.is_empty() {
1482 return idx_errs;
1483 }
1484
1485 let mut results = Vec::with_capacity(0);
1488
1489 let schema = self.get_schema();
1492
1493 let filt_all = filter!(f_pres(Attribute::Class));
1494 let all_entries = match self.internal_search(filt_all) {
1495 Ok(a) => a,
1496 Err(_e) => return vec![Err(ConsistencyError::QueryServerSearchFailure)],
1497 };
1498
1499 for e in all_entries {
1500 e.verify(schema, &mut results)
1501 }
1502
1503 self.get_be_txn().verify_ruv(&mut results);
1505
1506 Plugins::run_verify(self, &mut results);
1512 results
1515 }
1516
1517 #[instrument(level = "debug", skip_all)]
1518 pub fn scim_entry_id_get_ext(
1519 &mut self,
1520 uuid: Uuid,
1521 class: EntryClass,
1522 query: ScimEntryGetQuery,
1523 ident: Identity,
1524 ) -> Result<ScimEntryKanidm, OperationError> {
1525 let filter_intent = filter!(f_and!([
1526 f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)),
1527 f_eq(Attribute::Class, class.into())
1528 ]));
1529
1530 let f_intent_valid = filter_intent
1531 .validate(self.get_schema())
1532 .map_err(OperationError::SchemaViolation)?;
1533
1534 let f_valid = f_intent_valid.clone().into_ignore_hidden();
1535
1536 let r_attrs = query
1537 .attributes
1538 .map(|attr_set| attr_set.into_iter().collect());
1539
1540 let se = SearchEvent {
1541 ident,
1542 filter: f_valid,
1543 filter_orig: f_intent_valid,
1544 attrs: r_attrs,
1545 effective_access_check: query.ext_access_check,
1546 };
1547
1548 let mut vs = self.search_ext(&se)?;
1549 match vs.pop() {
1550 Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
1551 _ => {
1552 if vs.is_empty() {
1553 Err(OperationError::NoMatchingEntries)
1554 } else {
1555 Err(OperationError::UniqueConstraintViolation)
1557 }
1558 }
1559 }
1560 }
1561
1562 #[instrument(level = "debug", skip_all)]
1563 pub fn scim_search_ext(
1564 &mut self,
1565 ident: Identity,
1566 filter: ScimFilter,
1567 query: ScimEntryGetQuery,
1568 ) -> Result<ScimListResponse, OperationError> {
1569 let filter = if let Some(ref user_filter) = query.filter {
1570 ScimFilter::And(Box::new(filter), Box::new(user_filter.clone()))
1571 } else {
1572 filter
1573 };
1574
1575 let filter_intent = Filter::from_scim_ro(&ident, &filter, self)?;
1576
1577 self.scim_search_filter_ext(ident, &filter_intent, query)
1578 }
1579
1580 pub fn scim_search_filter_ext(
1581 &mut self,
1582 ident: Identity,
1583 filter_intent: &Filter<FilterInvalid>,
1584 query: ScimEntryGetQuery,
1585 ) -> Result<ScimListResponse, OperationError> {
1586 let f_intent_valid = filter_intent
1587 .validate(self.get_schema())
1588 .map_err(OperationError::SchemaViolation)?;
1589
1590 let f_valid = f_intent_valid.clone().into_ignore_hidden();
1591
1592 let r_attrs = query
1593 .attributes
1594 .map(|attr_set| attr_set.into_iter().collect());
1595
1596 let se = SearchEvent {
1597 ident,
1598 filter: f_valid,
1599 filter_orig: f_intent_valid,
1600 attrs: r_attrs,
1601 effective_access_check: query.ext_access_check,
1602 };
1603
1604 let mut result_set = self.search_ext(&se)?;
1605
1606 let total_results = result_set.len() as u64;
1608
1609 if let Some(sort_attr) = query.sort_by {
1615 result_set.sort_unstable_by(|entry_left, entry_right| {
1616 let left = entry_left.get_ava_set(&sort_attr);
1617 let right = entry_right.get_ava_set(&sort_attr);
1618 match (left, right) {
1619 (Some(left), Some(right)) => left.cmp(right),
1620 (Some(_), None) => std::cmp::Ordering::Less,
1621 (None, Some(_)) => std::cmp::Ordering::Greater,
1622 (None, None) => std::cmp::Ordering::Equal,
1623 }
1624 });
1625 }
1626
1627 let (items_per_page, start_index, paginated_result_set) = if let Some(count) = query.count {
1629 let count: u64 = count.get();
1630 let start_index: u64 = query
1633 .start_index
1634 .map(|non_zero_index|
1635 non_zero_index.get() - 1)
1637 .unwrap_or_default();
1638
1639 if start_index as usize > result_set.len() {
1641 return Err(OperationError::SC0029PaginationOutOfBounds);
1644 }
1645
1646 let mut result_set = result_set.split_off(start_index as usize);
1647 result_set.truncate(count as usize);
1648
1649 (
1650 NonZeroU64::new(count),
1651 NonZeroU64::new(start_index + 1),
1652 result_set,
1653 )
1654 } else {
1655 (None, None, result_set)
1657 };
1658
1659 let resources = paginated_result_set
1660 .into_iter()
1661 .map(|entry| entry.to_scim_kanidm(self))
1662 .collect::<Result<Vec<_>, _>>()?;
1663
1664 Ok(ScimListResponse {
1665 schemas: Vec::with_capacity(0),
1667 total_results,
1668 items_per_page,
1669 start_index,
1670 resources,
1671 })
1672 }
1673
1674 #[instrument(level = "debug", skip_all)]
1675 pub fn scim_search_message_ready_ext(
1676 &mut self,
1677 ident: Identity,
1678 curtime: Duration,
1679 ) -> Result<ScimListResponse, OperationError> {
1680 let curtime_odt = OffsetDateTime::UNIX_EPOCH + curtime;
1681
1682 let filter_intent = filter_all!(f_and(vec![
1683 f_eq(Attribute::Class, EntryClass::OutboundMessage.into()),
1684 f_lt(Attribute::SendAfter, PartialValue::DateTime(curtime_odt)),
1685 f_andnot(f_pres(Attribute::SentAt))
1686 ]));
1687
1688 let query = ScimEntryGetQuery::default();
1689
1690 self.scim_search_filter_ext(ident, &filter_intent, query)
1691 }
1692}
1693
1694impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
1695 type AccessControlsTransactionType = AccessControlsWriteTransaction<'a>;
1696 type BackendTransactionType = BackendWriteTransaction<'a>;
1697 type SchemaTransactionType = SchemaWriteTransaction<'a>;
1698 type KeyProvidersTransactionType = KeyProvidersWriteTransaction<'a>;
1699
1700 fn get_be_txn(&mut self) -> &mut BackendWriteTransaction<'a> {
1701 &mut self.be_txn
1702 }
1703
1704 fn get_schema<'b>(&self) -> &'b SchemaWriteTransaction<'a> {
1705 unsafe {
1709 let s = (&self.schema) as *const _;
1710 &*s
1711 }
1712 }
1713
1714 fn get_accesscontrols(&self) -> &AccessControlsWriteTransaction<'a> {
1715 &self.accesscontrols
1716 }
1717
1718 fn get_key_providers(&self) -> &KeyProvidersWriteTransaction<'a> {
1719 &self.key_providers
1720 }
1721
1722 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1723 if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1724 None
1725 } else {
1726 Some(&mut self.resolve_filter_cache)
1727 }
1728 }
1729
1730 fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig {
1731 &self.feature_config.hmac_name_history
1732 }
1733
1734 fn get_resolve_filter_cache_and_be_txn(
1735 &mut self,
1736 ) -> (
1737 &mut BackendWriteTransaction<'a>,
1738 Option<&mut ResolveFilterCacheReadTxn<'a>>,
1739 ) {
1740 if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1741 (&mut self.be_txn, None)
1742 } else {
1743 (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1744 }
1745 }
1746
1747 fn pw_badlist(&self) -> &HashSet<String> {
1748 &self.system_config.pw_badlist
1749 }
1750
1751 fn denied_names(&self) -> &HashSet<String> {
1752 &self.system_config.denied_names
1753 }
1754
1755 fn get_domain_version(&self) -> DomainVersion {
1756 self.d_info.d_vers
1757 }
1758
1759 fn get_domain_patch_level(&self) -> u32 {
1760 self.d_info.d_patch_level
1761 }
1762
1763 fn get_domain_development_taint(&self) -> bool {
1764 self.d_info.d_devel_taint
1765 }
1766
1767 fn get_domain_uuid(&self) -> Uuid {
1768 self.d_info.d_uuid
1769 }
1770
1771 fn get_domain_name(&self) -> &str {
1773 &self.d_info.d_name
1774 }
1775
1776 fn get_domain_display_name(&self) -> &str {
1777 &self.d_info.d_display
1778 }
1779
1780 fn get_domain_image_value(&self) -> Option<ImageValue> {
1781 self.d_info.d_image.clone()
1782 }
1783}
1784
1785impl QueryServer {
1786 pub fn new(
1787 be: Backend,
1788 schema: Schema,
1789 domain_name: String,
1790 curtime: Duration,
1791 ) -> Result<Self, OperationError> {
1792 let (s_uuid, d_uuid, ts_max) = {
1793 let mut wr = be.write()?;
1794 let s_uuid = wr.get_db_s_uuid()?;
1795 let d_uuid = wr.get_db_d_uuid()?;
1796 let ts_max = wr.get_db_ts_max(curtime)?;
1797 wr.commit()?;
1798 (s_uuid, d_uuid, ts_max)
1799 };
1800
1801 let pool_size = be.get_pool_size();
1802
1803 debug!("Server UUID -> {:?}", s_uuid);
1804 debug!("Domain UUID -> {:?}", d_uuid);
1805 debug!("Domain Name -> {:?}", domain_name);
1806
1807 let d_info = Arc::new(CowCell::new(DomainInfo {
1808 d_uuid,
1809 d_vers: DOMAIN_LEVEL_0,
1812 d_patch_level: 0,
1813 d_name: domain_name.clone(),
1814 d_display: domain_name,
1817 d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(),
1819 d_ldap_allow_unix_pw_bind: false,
1820 d_allow_easter_eggs: false,
1821 d_image: None,
1822 }));
1823
1824 let cid = Cid::new_lamport(s_uuid, curtime, &ts_max);
1825 let cid_max = Arc::new(CowCell::new(cid));
1826
1827 let system_config = Arc::new(CowCell::new(SystemConfig::default()));
1829
1830 let feature_config = Arc::new(CowCell::new(FeatureConfig::default()));
1831
1832 let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
1833
1834 let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
1835
1836 let resolve_filter_cache = Arc::new(
1837 ARCacheBuilder::new()
1838 .set_size(RESOLVE_FILTER_CACHE_MAX, RESOLVE_FILTER_CACHE_LOCAL)
1839 .set_reader_quiesce(true)
1840 .build()
1841 .ok_or_else(|| {
1842 error!("Failed to build filter resolve cache");
1843 OperationError::DB0003FilterResolveCacheBuild
1844 })?,
1845 );
1846
1847 let key_providers = Arc::new(KeyProviders::default());
1848
1849 debug_assert!(pool_size > 0);
1852 let read_ticket_pool = std::cmp::max(pool_size - 1, 1);
1853
1854 Ok(QueryServer {
1855 phase,
1856 d_info,
1857 system_config,
1858 feature_config,
1859 be,
1860 schema: Arc::new(schema),
1861 accesscontrols: Arc::new(AccessControls::default()),
1862 db_tickets: Arc::new(Semaphore::new(pool_size as usize)),
1863 read_tickets: Arc::new(Semaphore::new(read_ticket_pool as usize)),
1864 write_ticket: Arc::new(Semaphore::new(1)),
1865 resolve_filter_cache,
1866 dyngroup_cache,
1867 cid_max,
1868 key_providers,
1869 })
1870 }
1871
1872 pub fn try_quiesce(&self) {
1873 self.be.try_quiesce();
1874 self.accesscontrols.try_quiesce();
1875 self.resolve_filter_cache.try_quiesce();
1876 }
1877
1878 #[instrument(level = "debug", skip_all)]
1879 async fn read_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
1880 let read_ticket = if cfg!(test) {
1884 self.read_tickets
1885 .try_acquire()
1886 .inspect_err(|err| {
1887 error!(?err, "Unable to acquire read ticket!");
1888 })
1889 .ok()?
1890 } else {
1891 let fut = tokio::time::timeout(
1892 Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
1893 self.read_tickets.acquire(),
1894 );
1895
1896 match fut.await {
1897 Ok(Ok(ticket)) => ticket,
1898 Ok(Err(_)) => {
1899 error!("Failed to acquire read ticket, may be poisoned.");
1900 return None;
1901 }
1902 Err(_) => {
1903 error!("Failed to acquire read ticket, server is overloaded.");
1904 return None;
1905 }
1906 }
1907 };
1908
1909 let db_ticket = if cfg!(test) {
1914 self.db_tickets
1915 .try_acquire()
1916 .inspect_err(|err| {
1917 error!(?err, "Unable to acquire database ticket!");
1918 })
1919 .ok()?
1920 } else {
1921 self.db_tickets
1922 .acquire()
1923 .await
1924 .inspect_err(|err| {
1925 error!(?err, "Unable to acquire database ticket!");
1926 })
1927 .ok()?
1928 };
1929
1930 Some((read_ticket, db_ticket))
1931 }
1932
1933 pub async fn read(&self) -> Result<QueryServerReadTransaction<'_>, OperationError> {
1934 let (read_ticket, db_ticket) = self
1935 .read_acquire_ticket()
1936 .await
1937 .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
1938 let schema = self.schema.read();
1942
1943 let cid_max = self.cid_max.read();
1944 let trim_cid = cid_max.sub_secs(CHANGELOG_MAX_AGE)?;
1945
1946 let be_txn = self.be.read()?;
1947
1948 Ok(QueryServerReadTransaction {
1949 be_txn,
1950 schema,
1951 d_info: self.d_info.read(),
1952 system_config: self.system_config.read(),
1953 feature_config: self.feature_config.read(),
1954 accesscontrols: self.accesscontrols.read(),
1955 key_providers: self.key_providers.read(),
1956 _db_ticket: db_ticket,
1957 _read_ticket: read_ticket,
1958 resolve_filter_cache: self.resolve_filter_cache.read(),
1959 trim_cid,
1960 })
1961 }
1962
1963 #[instrument(level = "debug", skip_all)]
1964 async fn write_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
1965 let write_ticket = if cfg!(test) {
1967 self.write_ticket
1968 .try_acquire()
1969 .inspect_err(|err| {
1970 error!(?err, "Unable to acquire write ticket!");
1971 })
1972 .ok()?
1973 } else {
1974 let fut = tokio::time::timeout(
1975 Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
1976 self.write_ticket.acquire(),
1977 );
1978
1979 match fut.await {
1980 Ok(Ok(ticket)) => ticket,
1981 Ok(Err(_)) => {
1982 error!("Failed to acquire write ticket, may be poisoned.");
1983 return None;
1984 }
1985 Err(_) => {
1986 error!("Failed to acquire write ticket, server is overloaded.");
1987 return None;
1988 }
1989 }
1990 };
1991
1992 let db_ticket = if cfg!(test) {
1996 self.db_tickets
1997 .try_acquire()
1998 .inspect_err(|err| {
1999 error!(?err, "Unable to acquire write db_ticket!");
2000 })
2001 .ok()?
2002 } else {
2003 self.db_tickets
2004 .acquire()
2005 .await
2006 .inspect_err(|err| {
2007 error!(?err, "Unable to acquire write db_ticket!");
2008 })
2009 .ok()?
2010 };
2011
2012 Some((write_ticket, db_ticket))
2013 }
2014
2015 pub async fn write(
2016 &self,
2017 curtime: Duration,
2018 ) -> Result<QueryServerWriteTransaction<'_>, OperationError> {
2019 let (write_ticket, db_ticket) = self
2020 .write_acquire_ticket()
2021 .await
2022 .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
2023
2024 let be_txn = self.be.write()?;
2029
2030 let schema_write = self.schema.write();
2031 let d_info = self.d_info.write();
2032 let system_config = self.system_config.write();
2033 let feature_config = self.feature_config.write();
2034 let phase = self.phase.write();
2035
2036 let mut cid = self.cid_max.write();
2037 *cid = Cid::new_lamport(cid.s_uuid, curtime, &cid.ts);
2039
2040 let trim_cid = cid.sub_secs(CHANGELOG_MAX_AGE)?;
2041
2042 Ok(QueryServerWriteTransaction {
2043 committed: false,
2050 phase,
2051 d_info,
2052 system_config,
2053 feature_config,
2054 curtime,
2055 cid,
2056 trim_cid,
2057 be_txn,
2058 schema: schema_write,
2059 accesscontrols: self.accesscontrols.write(),
2060 changed_flags: ChangeFlag::empty(),
2061 changed_uuid: HashSet::new(),
2062 _db_ticket: db_ticket,
2063 _write_ticket: write_ticket,
2064 resolve_filter_cache: self.resolve_filter_cache.read(),
2065 resolve_filter_cache_clear: false,
2066 resolve_filter_cache_write: self.resolve_filter_cache.write(),
2067 dyngroup_cache: self.dyngroup_cache.write(),
2068 key_providers: self.key_providers.write(),
2069 })
2070 }
2071
2072 #[cfg(any(test, debug_assertions))]
2073 pub async fn clear_cache(&self) -> Result<(), OperationError> {
2074 let ct = duration_from_epoch_now();
2075 let mut w_txn = self.write(ct).await?;
2076 w_txn.clear_cache()?;
2077 w_txn.commit()
2078 }
2079
2080 pub async fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
2081 let current_time = duration_from_epoch_now();
2082 if self
2087 .write(current_time)
2088 .await
2089 .and_then(|mut txn| {
2090 txn.force_schema_reload();
2091 txn.commit()
2092 })
2093 .is_err()
2094 {
2095 return vec![Err(ConsistencyError::Unknown)];
2096 };
2097
2098 match self.read().await {
2099 Ok(mut r_txn) => r_txn.verify(),
2100 Err(_) => vec![Err(ConsistencyError::Unknown)],
2101 }
2102 }
2103}
2104
2105impl<'a> QueryServerWriteTransaction<'a> {
2106 pub(crate) fn get_server_uuid(&self) -> Uuid {
2107 self.cid.s_uuid
2109 }
2110
2111 pub(crate) fn reset_server_uuid(&mut self) -> Result<(), OperationError> {
2112 let s_uuid = self.be_txn.reset_db_s_uuid().map_err(|err| {
2113 error!(?err, "Failed to reset server replication uuid");
2114 err
2115 })?;
2116
2117 debug!(?s_uuid, "reset server replication uuid");
2118
2119 self.cid.s_uuid = s_uuid;
2120
2121 Ok(())
2122 }
2123
2124 pub(crate) fn get_curtime(&self) -> Duration {
2125 self.curtime
2126 }
2127
2128 pub(crate) fn get_curtime_odt(&self) -> OffsetDateTime {
2129 OffsetDateTime::UNIX_EPOCH + self.curtime
2130 }
2131
2132 pub(crate) fn get_cid(&self) -> &Cid {
2133 &self.cid
2134 }
2135
2136 pub(crate) fn get_key_providers_mut(&mut self) -> &mut KeyProvidersWriteTransaction<'a> {
2137 &mut self.key_providers
2138 }
2139
2140 pub(crate) fn get_dyngroup_cache(&mut self) -> &mut DynGroupCache {
2141 &mut self.dyngroup_cache
2142 }
2143
2144 pub fn domain_raise(&mut self, level: u32) -> Result<(), OperationError> {
2145 if level > DOMAIN_MAX_LEVEL {
2146 return Err(OperationError::MG0002RaiseDomainLevelExceedsMaximum);
2147 }
2148
2149 let modl = ModifyList::new_purge_and_set(Attribute::Version, Value::Uint32(level));
2150 let udi = PVUUID_DOMAIN_INFO.clone();
2151 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2152 self.internal_modify(&filt, &modl)
2153 }
2154
2155 pub fn domain_remigrate(&mut self, level: u32) -> Result<(), OperationError> {
2156 let mut_d_info = self.d_info.get_mut();
2157
2158 if level > mut_d_info.d_vers {
2159 return Ok(());
2161 } else if level < DOMAIN_MIN_REMIGRATION_LEVEL {
2162 return Err(OperationError::MG0001InvalidReMigrationLevel);
2163 };
2164
2165 info!(
2166 "Prepare to re-migrate from {} -> {}",
2167 level, mut_d_info.d_vers
2168 );
2169 mut_d_info.d_vers = level;
2170 self.changed_flags.insert(ChangeFlag::DOMAIN);
2171
2172 Ok(())
2173 }
2174
2175 #[instrument(level = "debug", skip_all)]
2176 pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> {
2177 let filt = filter!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2180 let res = self.internal_search(filt).map_err(|e| {
2181 admin_error!("reload schema internal search failed {:?}", e);
2182 e
2183 })?;
2184 let attributetypes: Result<Vec<_>, _> =
2186 res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
2187
2188 let attributetypes = attributetypes.map_err(|e| {
2189 admin_error!("reload schema attributetypes {:?}", e);
2190 e
2191 })?;
2192
2193 self.schema.update_attributes(attributetypes).map_err(|e| {
2194 admin_error!("reload schema update attributetypes {:?}", e);
2195 e
2196 })?;
2197
2198 let filt = filter!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
2200 let res = self.internal_search(filt).map_err(|e| {
2201 admin_error!("reload schema internal search failed {:?}", e);
2202 e
2203 })?;
2204 let classtypes: Result<Vec<_>, _> = res.iter().map(|e| SchemaClass::try_from(e)).collect();
2206 let classtypes = classtypes.map_err(|e| {
2207 admin_error!("reload schema classtypes {:?}", e);
2208 e
2209 })?;
2210
2211 self.schema.update_classes(classtypes).map_err(|e| {
2212 admin_error!("reload schema update classtypes {:?}", e);
2213 e
2214 })?;
2215
2216 let valid_r = self.schema.validate();
2218
2219 if valid_r.is_empty() {
2221 trace!("Reloading idxmeta ...");
2223 self.be_txn
2224 .update_idxmeta(self.schema.reload_idxmeta())
2225 .map_err(|e| {
2226 admin_error!("reload schema update idxmeta {:?}", e);
2227 e
2228 })
2229 } else {
2230 admin_error!("Schema reload failed -> {:?}", valid_r);
2232 Err(OperationError::ConsistencyError(
2233 valid_r.into_iter().filter_map(|v| v.err()).collect(),
2234 ))
2235 }?;
2236
2237 self.resolve_filter_cache_clear = true;
2240
2241 DynGroup::reload(self)?;
2244
2245 Ok(())
2246 }
2247
2248 #[instrument(level = "debug", skip_all)]
2249 fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
2250 trace!("ACP reload started ...");
2258
2259 let filt = filter!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
2262
2263 let res = self.internal_search(filt).map_err(|e| {
2264 admin_error!(
2265 err = ?e,
2266 "reload accesscontrols internal search failed",
2267 );
2268 e
2269 })?;
2270
2271 let sync_agreement_map: HashMap<Uuid, BTreeSet<Attribute>> = res
2272 .iter()
2273 .filter_map(|e| {
2274 e.get_ava_as_iutf8(Attribute::SyncYieldAuthority)
2275 .map(|set| {
2276 let set: BTreeSet<_> =
2277 set.iter().map(|s| Attribute::from(s.as_str())).collect();
2278 (e.get_uuid(), set)
2279 })
2280 })
2281 .collect();
2282
2283 self.accesscontrols
2284 .update_sync_agreements(sync_agreement_map);
2285
2286 let filt = filter!(f_and!([
2288 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2289 f_eq(Attribute::Class, EntryClass::AccessControlSearch.into()),
2290 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2291 ]));
2292
2293 let res = self.internal_search(filt).map_err(|e| {
2294 admin_error!(
2295 err = ?e,
2296 "reload accesscontrols internal search failed",
2297 );
2298 e
2299 })?;
2300 let search_acps: Result<Vec<_>, _> = res
2301 .iter()
2302 .map(|e| AccessControlSearch::try_from(self, e))
2303 .collect();
2304
2305 let search_acps = search_acps.map_err(|e| {
2306 admin_error!(err = ?e, "Unable to parse search accesscontrols");
2307 e
2308 })?;
2309
2310 self.accesscontrols
2311 .update_search(search_acps)
2312 .map_err(|e| {
2313 admin_error!(err = ?e, "Failed to update search accesscontrols");
2314 e
2315 })?;
2316 let filt = filter!(f_and!([
2318 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2319 f_eq(Attribute::Class, EntryClass::AccessControlCreate.into()),
2320 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2321 ]));
2322
2323 let res = self.internal_search(filt).map_err(|e| {
2324 admin_error!(
2325 err = ?e,
2326 "reload accesscontrols internal search failed"
2327 );
2328 e
2329 })?;
2330 let create_acps: Result<Vec<_>, _> = res
2331 .iter()
2332 .map(|e| AccessControlCreate::try_from(self, e))
2333 .collect();
2334
2335 let create_acps = create_acps.map_err(|e| {
2336 admin_error!(err = ?e, "Unable to parse create accesscontrols");
2337 e
2338 })?;
2339
2340 self.accesscontrols
2341 .update_create(create_acps)
2342 .map_err(|e| {
2343 admin_error!(err = ?e, "Failed to update create accesscontrols");
2344 e
2345 })?;
2346 let filt = filter!(f_and!([
2348 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2349 f_eq(Attribute::Class, EntryClass::AccessControlModify.into()),
2350 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2351 ]));
2352
2353 let res = self.internal_search(filt).map_err(|e| {
2354 admin_error!("reload accesscontrols internal search failed {:?}", e);
2355 e
2356 })?;
2357 let modify_acps: Result<Vec<_>, _> = res
2358 .iter()
2359 .map(|e| AccessControlModify::try_from(self, e))
2360 .collect();
2361
2362 let modify_acps = modify_acps.map_err(|e| {
2363 admin_error!("Unable to parse modify accesscontrols {:?}", e);
2364 e
2365 })?;
2366
2367 self.accesscontrols
2368 .update_modify(modify_acps)
2369 .map_err(|e| {
2370 admin_error!("Failed to update modify accesscontrols {:?}", e);
2371 e
2372 })?;
2373 let filt = filter!(f_and!([
2375 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2376 f_eq(Attribute::Class, EntryClass::AccessControlDelete.into()),
2377 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2378 ]));
2379
2380 let res = self.internal_search(filt).map_err(|e| {
2381 admin_error!("reload accesscontrols internal search failed {:?}", e);
2382 e
2383 })?;
2384 let delete_acps: Result<Vec<_>, _> = res
2385 .iter()
2386 .map(|e| AccessControlDelete::try_from(self, e))
2387 .collect();
2388
2389 let delete_acps = delete_acps.map_err(|e| {
2390 admin_error!("Unable to parse delete accesscontrols {:?}", e);
2391 e
2392 })?;
2393
2394 self.accesscontrols.update_delete(delete_acps).map_err(|e| {
2395 admin_error!("Failed to update delete accesscontrols {:?}", e);
2396 e
2397 })
2398 }
2399
2400 #[instrument(level = "debug", skip_all)]
2401 pub(crate) fn reload_key_material(&mut self) -> Result<(), OperationError> {
2402 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
2403
2404 let res = self.internal_search(filt).map_err(|e| {
2405 admin_error!(
2406 err = ?e,
2407 "reload key providers internal search failed",
2408 );
2409 e
2410 })?;
2411
2412 let providers = res
2415 .iter()
2416 .map(|e| KeyProvider::try_from(e).and_then(|kp| kp.test().map(|()| kp)))
2417 .collect::<Result<Vec<_>, _>>()?;
2418
2419 self.key_providers.update_providers(providers)?;
2420
2421 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
2422
2423 let res = self.internal_search(filt).map_err(|e| {
2424 admin_error!(
2425 err = ?e,
2426 "reload key objects internal search failed",
2427 );
2428 e
2429 })?;
2430
2431 res.iter()
2432 .try_for_each(|entry| self.key_providers.load_key_object(entry.as_ref()))
2433 }
2434
2435 #[instrument(level = "debug", skip_all)]
2436 pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
2437 let denied_names = self.get_sc_denied_names()?;
2438 let pw_badlist = self.get_sc_password_badlist()?;
2439
2440 let mut_system_config = self.system_config.get_mut();
2441 mut_system_config.denied_names = denied_names;
2442 mut_system_config.pw_badlist = pw_badlist;
2443 Ok(())
2444 }
2445
2446 #[instrument(level = "debug", skip_all)]
2448 pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
2449 let domain_info = self.internal_search_uuid(UUID_DOMAIN_INFO).map_err(|err| {
2450 error!(?err, "Error getting domain info");
2451 err
2452 })?;
2453
2454 let domain_info_version = domain_info
2455 .get_ava_single_uint32(Attribute::Version)
2456 .ok_or_else(|| {
2457 error!("domain info missing attribute version");
2458 OperationError::InvalidEntryState
2459 })?;
2460
2461 let domain_info_patch_level = domain_info
2462 .get_ava_single_uint32(Attribute::PatchLevel)
2463 .unwrap_or(0);
2464
2465 let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
2469 let domain_info_devel_taint = current_devel_flag
2470 || domain_info
2471 .get_ava_single_bool(Attribute::DomainDevelopmentTaint)
2472 .unwrap_or_default();
2473
2474 let domain_allow_easter_eggs = domain_info
2475 .get_ava_single_bool(Attribute::DomainAllowEasterEggs)
2476 .unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some());
2478
2479 let mut_d_info = self.d_info.get_mut();
2483 let previous_version = mut_d_info.d_vers;
2484 let previous_patch_level = mut_d_info.d_patch_level;
2485 mut_d_info.d_vers = domain_info_version;
2486 mut_d_info.d_patch_level = domain_info_patch_level;
2487 mut_d_info.d_devel_taint = domain_info_devel_taint;
2488 mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs;
2489
2490 if (previous_version == domain_info_version
2493 && previous_patch_level == domain_info_patch_level)
2494 || *self.phase < ServerPhase::DomainInfoReady
2495 {
2496 return Ok(());
2497 }
2498
2499 debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2500 debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2501
2502 if previous_version == DOMAIN_LEVEL_0 {
2506 debug!(
2507 "Server was just brought up, skipping migrations as we are already at target level"
2508 );
2509 return Ok(());
2510 }
2511
2512 if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL {
2513 error!("UNABLE TO PROCEED. You are attempting a Skip update which is NOT SUPPORTED. You must upgrade one-version of Kanidm at a time.");
2514 error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
2515 error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2516 error!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2517 return Err(OperationError::MG0008SkipUpgradeAttempted);
2518 }
2519
2520 if previous_version <= DOMAIN_LEVEL_8 && domain_info_version >= DOMAIN_LEVEL_9 {
2521 self.migrate_domain_8_to_9()?;
2523 }
2524
2525 if previous_patch_level < PATCH_LEVEL_2
2526 && domain_info_patch_level >= PATCH_LEVEL_2
2527 && domain_info_version == DOMAIN_LEVEL_9
2528 {
2529 self.migrate_domain_patch_level_2()?;
2530 }
2531
2532 if previous_version <= DOMAIN_LEVEL_9 && domain_info_version >= DOMAIN_LEVEL_10 {
2533 self.migrate_domain_9_to_10()?;
2535 }
2536
2537 if previous_version <= DOMAIN_LEVEL_10 && domain_info_version >= DOMAIN_LEVEL_11 {
2538 self.migrate_domain_10_to_11()?;
2540 }
2541
2542 if previous_version <= DOMAIN_LEVEL_11 && domain_info_version >= DOMAIN_LEVEL_12 {
2543 self.migrate_domain_11_to_12()?;
2545 }
2546
2547 if previous_version <= DOMAIN_LEVEL_12 && domain_info_version >= DOMAIN_LEVEL_13 {
2548 self.migrate_domain_12_to_13()?;
2550 }
2551
2552 debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
2556
2557 Ok(())
2558 }
2559
2560 #[instrument(level = "debug", skip_all)]
2562 pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
2563 let domain_entry = self.get_db_domain()?;
2564
2565 let domain_name = domain_entry
2566 .get_ava_single_iname(Attribute::DomainName)
2567 .map(str::to_string)
2568 .ok_or(OperationError::InvalidEntryState)?;
2569
2570 let display_name = domain_entry
2571 .get_ava_single_utf8(Attribute::DomainDisplayName)
2572 .map(str::to_string)
2573 .unwrap_or_else(|| format!("Kanidm {domain_name}"));
2574
2575 let domain_ldap_allow_unix_pw_bind = domain_entry
2576 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
2577 .unwrap_or(true);
2578
2579 let domain_image = domain_entry.get_ava_single_image(Attribute::Image);
2580
2581 let domain_uuid = self.be_txn.get_db_d_uuid()?;
2582
2583 let mut_d_info = self.d_info.get_mut();
2584 mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind;
2585 if mut_d_info.d_uuid != domain_uuid {
2586 admin_warn!(
2587 "Using domain uuid from the database {} - was {} in memory",
2588 domain_name,
2589 mut_d_info.d_name,
2590 );
2591 mut_d_info.d_uuid = domain_uuid;
2592 }
2593 if mut_d_info.d_name != domain_name {
2594 admin_warn!(
2595 "Using domain name from the database {} - was {} in memory",
2596 domain_name,
2597 mut_d_info.d_name,
2598 );
2599 admin_warn!(
2600 "If you think this is an error, see https://kanidm.github.io/kanidm/master/domain_rename.html"
2601 );
2602 mut_d_info.d_name = domain_name;
2603 }
2604 mut_d_info.d_display = display_name;
2605 mut_d_info.d_image = domain_image;
2606 Ok(())
2607 }
2608
2609 #[instrument(level = "debug", skip_all)]
2611 pub(crate) fn reload_feature_config(&mut self) -> Result<(), OperationError> {
2612 let filt = filter!(f_eq(Attribute::Class, EntryClass::Feature.into()));
2613
2614 let feature_configs = self.internal_search(filt).inspect_err(|err| {
2615 error!(?err, "reload feature configuration internal search failed",)
2616 })?;
2617
2618 let current_time = self.get_curtime();
2619 let domain_level = self.get_domain_version();
2620
2621 let mut hmac_name_history_fixup = false;
2622
2623 for feature_entry in feature_configs {
2627 match feature_entry.get_uuid() {
2628 UUID_HMAC_NAME_FEATURE => {
2629 if domain_level < DOMAIN_LEVEL_12 {
2630 trace!("Skipping hmac name history config");
2631 continue;
2632 }
2633
2634 let key_object = self
2635 .get_key_providers()
2636 .get_key_object_handle(UUID_HMAC_NAME_FEATURE)
2637 .ok_or(OperationError::KP0079KeyObjectNotFound)?;
2638
2639 let mut key = HmacSha256Key::default();
2640 key_object.hkdf_s256_expand(
2641 UUID_HMAC_NAME_FEATURE.as_bytes(),
2642 key.as_mut_slice(),
2643 current_time,
2644 )?;
2645
2646 drop(key_object);
2647
2648 let new_feature_enabled_state = feature_entry
2649 .get_ava_single_bool(Attribute::Enabled)
2650 .unwrap_or_default();
2651
2652 let feature_config_txn = self.feature_config.get_mut();
2653
2654 hmac_name_history_fixup =
2655 !feature_config_txn.hmac_name_history.enabled && new_feature_enabled_state;
2656
2657 feature_config_txn.hmac_name_history.enabled = new_feature_enabled_state;
2658
2659 std::mem::swap(&mut key, &mut feature_config_txn.hmac_name_history.key);
2660 }
2661 feature_uuid => {
2662 error!(
2663 ?feature_uuid,
2664 "Unrecognised feature uuid, unable to proceed"
2665 );
2666 return Err(OperationError::KG004UnknownFeatureUuid);
2667 }
2668 }
2669 }
2670
2671 if hmac_name_history_fixup {
2672 plugins::hmac_name_unique::HmacNameUnique::fixup(self)?;
2673 }
2674
2675 Ok(())
2676 }
2677
2678 pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2682 let modl = ModifyList::new_purge_and_set(
2683 Attribute::DomainDisplayName,
2684 Value::new_utf8(new_domain_name.to_string()),
2685 );
2686 let udi = PVUUID_DOMAIN_INFO.clone();
2687 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2688 self.internal_modify(&filt, &modl)
2689 }
2690
2691 pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2704 let modl =
2705 ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name));
2706 let udi = PVUUID_DOMAIN_INFO.clone();
2707 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2708 self.internal_modify(&filt, &modl)
2709 }
2710
2711 pub fn reindex(&mut self, immediate: bool) -> Result<(), OperationError> {
2712 self.be_txn.reindex(immediate)
2716 }
2717
2718 fn force_schema_reload(&mut self) {
2719 self.changed_flags.insert(ChangeFlag::SCHEMA);
2720 }
2721
2722 fn force_domain_reload(&mut self) {
2723 self.changed_flags.insert(ChangeFlag::DOMAIN);
2724 }
2725
2726 pub(crate) fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> {
2727 self.be_txn.upgrade_reindex(v)
2728 }
2729
2730 #[inline]
2731 pub(crate) fn get_changed_app(&self) -> bool {
2732 self.changed_flags.contains(ChangeFlag::APPLICATION)
2733 }
2734
2735 #[inline]
2736 pub(crate) fn get_changed_oauth2(&self) -> bool {
2737 self.changed_flags.contains(ChangeFlag::OAUTH2)
2738 }
2739
2740 #[inline]
2741 pub(crate) fn clear_changed_oauth2(&mut self) {
2742 self.changed_flags.remove(ChangeFlag::OAUTH2)
2743 }
2744
2745 #[inline]
2746 pub(crate) fn get_changed_oauth2_client(&self) -> bool {
2747 self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
2748 }
2749
2750 pub(crate) fn set_phase_bootstrap(&mut self) {
2753 *self.phase = ServerPhase::Bootstrap;
2754 }
2755
2756 pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
2758 if phase > *self.phase {
2760 *self.phase = phase
2761 }
2762 }
2763
2764 pub(crate) fn get_phase(&mut self) -> ServerPhase {
2765 *self.phase
2766 }
2767
2768 pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
2769 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2772 self.reload_domain_info_version()?;
2773 }
2774
2775 if self.changed_flags.intersects(ChangeFlag::SCHEMA) {
2780 self.reload_schema()?;
2781
2782 if *self.phase >= ServerPhase::Running {
2786 self.reindex(false)?;
2787 self.reload_schema()?;
2788 }
2789 }
2790
2791 if self
2794 .changed_flags
2795 .intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
2796 {
2797 self.reload_key_material()?;
2798 }
2799
2800 if self
2808 .changed_flags
2809 .intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
2810 {
2811 self.reload_accesscontrols()?;
2812 } else {
2813 }
2818
2819 if self.changed_flags.intersects(ChangeFlag::SYSTEM_CONFIG) {
2820 self.reload_system_config()?;
2821 }
2822
2823 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2824 self.reload_domain_info()?;
2825 }
2826
2827 if self
2828 .changed_flags
2829 .intersects(ChangeFlag::FEATURE | ChangeFlag::KEY_MATERIAL)
2830 {
2831 self.reload_feature_config()?;
2832 }
2833
2834 self.changed_flags.remove(
2836 ChangeFlag::DOMAIN
2837 | ChangeFlag::SCHEMA
2838 | ChangeFlag::FEATURE
2839 | ChangeFlag::SYSTEM_CONFIG
2840 | ChangeFlag::ACP
2841 | ChangeFlag::SYNC_AGREEMENT
2842 | ChangeFlag::KEY_MATERIAL,
2843 );
2844
2845 Ok(())
2846 }
2847
2848 #[cfg(any(test, debug_assertions))]
2849 #[instrument(level = "debug", skip_all)]
2850 pub fn clear_cache(&mut self) -> Result<(), OperationError> {
2851 self.be_txn.clear_cache()
2852 }
2853
2854 #[instrument(level = "debug", name="qswt_commit" skip_all)]
2855 pub fn commit(mut self) -> Result<(), OperationError> {
2856 self.reload()?;
2857
2858 let QueryServerWriteTransaction {
2860 committed,
2861 phase,
2862 d_info,
2863 system_config,
2864 feature_config,
2865 mut be_txn,
2866 schema,
2867 accesscontrols,
2868 cid,
2869 dyngroup_cache,
2870 key_providers,
2871 _db_ticket,
2873 _write_ticket,
2874 curtime: _,
2876 trim_cid: _,
2877 changed_flags,
2878 changed_uuid: _,
2879 resolve_filter_cache: _,
2880 resolve_filter_cache_clear,
2881 mut resolve_filter_cache_write,
2882 } = self;
2883 debug_assert!(!committed);
2884
2885 trace!(
2887 changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
2888 );
2889
2890 be_txn.set_db_ts_max(cid.ts)?;
2893 cid.commit();
2894
2895 if resolve_filter_cache_clear {
2897 resolve_filter_cache_write.clear();
2898 }
2899 resolve_filter_cache_write.commit();
2900
2901 schema
2905 .commit()
2906 .map(|_| d_info.commit())
2907 .map(|_| system_config.commit())
2908 .map(|_| feature_config.commit())
2909 .map(|_| phase.commit())
2910 .map(|_| dyngroup_cache.commit())
2911 .and_then(|_| key_providers.commit())
2912 .and_then(|_| accesscontrols.commit())
2913 .and_then(|_| be_txn.commit())
2914 }
2915
2916 pub(crate) fn get_txn_cid(&self) -> &Cid {
2917 &self.cid
2918 }
2919}
2920
2921#[cfg(test)]
2922mod tests {
2923 use crate::prelude::*;
2924 use kanidm_proto::scim_v1::{
2925 server::{ScimListResponse, ScimReference},
2926 JsonValue, ScimEntryGetQuery, ScimFilter,
2927 };
2928 use std::num::NonZeroU64;
2929
2930 #[qs_test]
2931 async fn test_name_to_uuid(server: &QueryServer) {
2932 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2933
2934 let t_uuid = Uuid::new_v4();
2935 assert!(server_txn
2936 .internal_create(vec![entry_init!(
2937 (Attribute::Class, EntryClass::Object.to_value()),
2938 (Attribute::Class, EntryClass::Account.to_value()),
2939 (Attribute::Class, EntryClass::Person.to_value()),
2940 (Attribute::Name, Value::new_iname("testperson1")),
2941 (Attribute::Uuid, Value::Uuid(t_uuid)),
2942 (Attribute::Description, Value::new_utf8s("testperson1")),
2943 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2944 ),])
2945 .is_ok());
2946
2947 let r1 = server_txn.name_to_uuid("testpers");
2949 assert!(r1.is_err());
2950 let r2 = server_txn.name_to_uuid("tEsTpErS");
2952 assert!(r2.is_err());
2953 let r3 = server_txn.name_to_uuid("testperson1");
2955 assert_eq!(r3, Ok(t_uuid));
2956 let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
2958 assert_eq!(r4, Ok(t_uuid));
2959 let r5 = server_txn.name_to_uuid("name=testperson1");
2961 assert_eq!(r5, Ok(t_uuid));
2962 let r6 = server_txn.name_to_uuid("name=testperson1,o=example");
2964 assert_eq!(r6, Ok(t_uuid));
2965 }
2966
2967 #[qs_test]
2968 async fn test_external_id_to_uuid(server: &QueryServer) {
2969 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2970
2971 let t_uuid = Uuid::new_v4();
2972 assert!(server_txn
2973 .internal_create(vec![entry_init!(
2974 (Attribute::Class, EntryClass::Object.to_value()),
2975 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2976 (Attribute::Uuid, Value::Uuid(t_uuid)),
2977 (
2978 Attribute::SyncExternalId,
2979 Value::new_iutf8("uid=testperson")
2980 )
2981 ),])
2982 .is_ok());
2983
2984 let r1 = server_txn.sync_external_id_to_uuid("tobias");
2986 assert_eq!(r1, Ok(None));
2987 let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
2989 assert_eq!(r2, Ok(None));
2990 let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
2992 assert_eq!(r3, Ok(Some(t_uuid)));
2993 let r4 = server_txn.sync_external_id_to_uuid("uId=TeStPeRsOn");
2995 assert_eq!(r4, Ok(Some(t_uuid)));
2996 }
2997
2998 #[qs_test]
2999 async fn test_uuid_to_spn(server: &QueryServer) {
3000 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3001
3002 let e1 = entry_init!(
3003 (Attribute::Class, EntryClass::Object.to_value()),
3004 (Attribute::Class, EntryClass::Person.to_value()),
3005 (Attribute::Class, EntryClass::Account.to_value()),
3006 (Attribute::Name, Value::new_iname("testperson1")),
3007 (
3008 Attribute::Uuid,
3009 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3010 ),
3011 (Attribute::Description, Value::new_utf8s("testperson1")),
3012 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3013 );
3014 let ce = CreateEvent::new_internal(vec![e1]);
3015 let cr = server_txn.create(&ce);
3016 assert!(cr.is_ok());
3017
3018 let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3020 assert_eq!(r1, Ok(None));
3022 let r3 = server_txn.uuid_to_spn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3024 println!("{r3:?}");
3025 assert_eq!(
3026 r3.unwrap().unwrap(),
3027 Value::new_spn_str("testperson1", "example.com")
3028 );
3029 let r4 = server_txn.uuid_to_spn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3031 assert_eq!(
3032 r4.unwrap().unwrap(),
3033 Value::new_spn_str("testperson1", "example.com")
3034 );
3035 }
3036
3037 #[qs_test]
3038 async fn test_uuid_to_rdn(server: &QueryServer) {
3039 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3040
3041 let e1 = entry_init!(
3042 (Attribute::Class, EntryClass::Object.to_value()),
3043 (Attribute::Class, EntryClass::Person.to_value()),
3044 (Attribute::Class, EntryClass::Account.to_value()),
3045 (Attribute::Name, Value::new_iname("testperson1")),
3046 (
3047 Attribute::Uuid,
3048 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3049 ),
3050 (Attribute::Description, Value::new_utf8s("testperson")),
3051 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3052 );
3053 let ce = CreateEvent::new_internal(vec![e1]);
3054 let cr = server_txn.create(&ce);
3055 assert!(cr.is_ok());
3056
3057 let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3059 assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
3061 let r3 = server_txn.uuid_to_rdn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3063 println!("{r3:?}");
3064 assert_eq!(r3.unwrap(), "spn=testperson1@example.com");
3065 let r4 = server_txn.uuid_to_rdn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3067 assert_eq!(r4.unwrap(), "spn=testperson1@example.com");
3068 }
3069
3070 #[qs_test]
3071 async fn test_clone_value(server: &QueryServer) {
3072 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3073 let e1 = entry_init!(
3074 (Attribute::Class, EntryClass::Object.to_value()),
3075 (Attribute::Class, EntryClass::Account.to_value()),
3076 (Attribute::Class, EntryClass::Person.to_value()),
3077 (Attribute::Name, Value::new_iname("testperson1")),
3078 (
3079 Attribute::Uuid,
3080 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3081 ),
3082 (Attribute::Description, Value::new_utf8s("testperson1")),
3083 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3084 );
3085 let ce = CreateEvent::new_internal(vec![e1]);
3086 let cr = server_txn.create(&ce);
3087 assert!(cr.is_ok());
3088
3089 let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
3091
3092 assert!(r1.is_err());
3093
3094 let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
3097
3098 assert!(r2.is_err());
3099
3100 let r3 = server_txn.clone_value(&Attribute::from("member"), "testperson1");
3102
3103 assert_eq!(
3104 r3,
3105 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3106 );
3107
3108 let r4 = server_txn.clone_value(
3110 &Attribute::from("member"),
3111 "cc8e95b4-c24f-4d68-ba54-8bed76f63930",
3112 );
3113
3114 debug!("{:?}", r4);
3115 assert_eq!(
3116 r4,
3117 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3118 );
3119 }
3120
3121 #[qs_test]
3122 async fn test_dynamic_schema_class(server: &QueryServer) {
3123 let e1 = entry_init!(
3124 (Attribute::Class, EntryClass::Object.to_value()),
3125 (Attribute::Class, EntryClass::TestClass.to_value()),
3126 (Attribute::Name, Value::new_iname("testobj1")),
3127 (
3128 Attribute::Uuid,
3129 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3130 )
3131 );
3132
3133 let e_cd = entry_init!(
3135 (Attribute::Class, EntryClass::Object.to_value()),
3136 (Attribute::Class, EntryClass::ClassType.to_value()),
3137 (Attribute::ClassName, EntryClass::TestClass.to_value()),
3138 (
3139 Attribute::Uuid,
3140 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3141 ),
3142 (Attribute::Description, Value::new_utf8s("Test Class")),
3143 (Attribute::May, Value::from(Attribute::Name))
3144 );
3145 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3146 let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
3148 assert!(server_txn.create(&ce_class).is_ok());
3149 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3151 assert!(server_txn.create(&ce_fail).is_err());
3152
3153 server_txn.commit().expect("should not fail");
3155
3156 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3158 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3161 assert!(server_txn.create(&ce_work).is_ok());
3162
3163 server_txn.commit().expect("should not fail");
3165
3166 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3168 let de_class = DeleteEvent::new_internal_invalid(filter!(f_eq(
3170 Attribute::ClassName,
3171 EntryClass::TestClass.into()
3172 )));
3173 assert!(server_txn.delete(&de_class).is_ok());
3174 server_txn.commit().expect("should not fail");
3176
3177 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3179 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3181 assert!(server_txn.create(&ce_fail).is_err());
3182 let testobj1 = server_txn
3184 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3185 .expect("failed");
3186 assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into()));
3187
3188 server_txn.commit().expect("should not fail");
3190 }
3192
3193 #[qs_test]
3194 async fn test_dynamic_schema_attr(server: &QueryServer) {
3195 let e1 = entry_init!(
3196 (Attribute::Class, EntryClass::Object.to_value()),
3197 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3198 (Attribute::Name, Value::new_iname("testobj1")),
3199 (
3200 Attribute::Uuid,
3201 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3202 ),
3203 (Attribute::TestAttr, Value::new_utf8s("test"))
3204 );
3205
3206 let e_ad = entry_init!(
3208 (Attribute::Class, EntryClass::Object.to_value()),
3209 (Attribute::Class, EntryClass::AttributeType.to_value()),
3210 (
3211 Attribute::Uuid,
3212 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3213 ),
3214 (Attribute::AttributeName, Value::from(Attribute::TestAttr)),
3215 (Attribute::Description, Value::new_utf8s("Test Attribute")),
3216 (Attribute::MultiValue, Value::new_bool(false)),
3217 (Attribute::Unique, Value::new_bool(false)),
3218 (
3219 Attribute::Syntax,
3220 Value::new_syntaxs("UTF8STRING").expect("syntax")
3221 )
3222 );
3223
3224 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3225 let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
3227 assert!(server_txn.create(&ce_attr).is_ok());
3228 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3230 assert!(server_txn.create(&ce_fail).is_err());
3231
3232 server_txn.commit().expect("should not fail");
3234
3235 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3237 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3240 assert!(server_txn.create(&ce_work).is_ok());
3241
3242 server_txn.commit().expect("should not fail");
3244
3245 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3247 let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
3249 Attribute::AttributeName,
3250 PartialValue::from(Attribute::TestAttr)
3251 )));
3252 assert!(server_txn.delete(&de_attr).is_ok());
3253 server_txn.commit().expect("should not fail");
3255
3256 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3258 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3260 assert!(server_txn.create(&ce_fail).is_err());
3261 let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
3263 assert!(server_txn.internal_search(filt).is_err());
3264 let testobj1 = server_txn
3267 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3268 .expect("failed");
3269 assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test")));
3270
3271 server_txn.commit().expect("should not fail");
3272 }
3274
3275 #[qs_test]
3276 async fn test_scim_entry_structure(server: &QueryServer) {
3277 let mut read_txn = server.read().await.unwrap();
3278
3279 let entry = read_txn
3281 .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
3282 .unwrap();
3283
3284 let reduced = entry.as_ref().clone().into_reduced();
3286 let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
3287
3288 assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE);
3290 let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap();
3291 match name_scim {
3292 ScimValueKanidm::String(name) => {
3293 assert_eq!(name.clone(), "idm_people_self_name_write")
3294 }
3295 _ => {
3296 panic!("expected String, actual {name_scim:?}");
3297 }
3298 }
3299
3300 let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap();
3302 match entry_managed_by_scim {
3303 ScimValueKanidm::EntryReferences(managed_by) => {
3304 assert_eq!(
3305 managed_by.first().unwrap().clone(),
3306 ScimReference {
3307 uuid: UUID_IDM_ADMINS,
3308 value: "idm_admins@example.com".to_string()
3309 }
3310 )
3311 }
3312 _ => {
3313 panic!("expected EntryReference, actual {entry_managed_by_scim:?}");
3314 }
3315 }
3316
3317 let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap();
3318 match members_scim {
3319 ScimValueKanidm::EntryReferences(members) => {
3320 assert_eq!(
3321 members.first().unwrap().clone(),
3322 ScimReference {
3323 uuid: UUID_IDM_ALL_PERSONS,
3324 value: "idm_all_persons@example.com".to_string()
3325 }
3326 )
3327 }
3328 _ => {
3329 panic!("expected EntryReferences, actual {members_scim:?}");
3330 }
3331 }
3332 }
3333
3334 #[qs_test]
3335 async fn test_scim_effective_access_query(server: &QueryServer) {
3336 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3337
3338 let group_uuid = Uuid::new_v4();
3339 let e1 = entry_init!(
3340 (Attribute::Class, EntryClass::Object.to_value()),
3341 (Attribute::Class, EntryClass::Group.to_value()),
3342 (Attribute::Name, Value::new_iname("testgroup")),
3343 (Attribute::Uuid, Value::Uuid(group_uuid))
3344 );
3345
3346 assert!(server_txn.internal_create(vec![e1]).is_ok());
3347 assert!(server_txn.commit().is_ok());
3348
3349 let mut server_txn = server.read().await.unwrap();
3352
3353 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3354 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3355
3356 let query = ScimEntryGetQuery {
3357 ext_access_check: true,
3358 ..Default::default()
3359 };
3360
3361 let scim_entry = server_txn
3362 .scim_entry_id_get_ext(group_uuid, EntryClass::Group, query, idm_admin_ident)
3363 .unwrap();
3364
3365 let ext_access_check = scim_entry.ext_access_check.unwrap();
3366
3367 trace!(?ext_access_check);
3368
3369 assert!(ext_access_check.delete);
3370 assert!(ext_access_check.search.check(&Attribute::DirectMemberOf));
3371 assert!(ext_access_check.search.check(&Attribute::MemberOf));
3372 assert!(ext_access_check.search.check(&Attribute::Name));
3373 assert!(ext_access_check.modify_present.check(&Attribute::Name));
3374 assert!(ext_access_check.modify_remove.check(&Attribute::Name));
3375 }
3376
3377 #[qs_test]
3378 async fn test_scim_basic_search_ext_query(server: &QueryServer) {
3379 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3380
3381 let group_uuid = Uuid::new_v4();
3382 let e1 = entry_init!(
3383 (Attribute::Class, EntryClass::Object.to_value()),
3384 (Attribute::Class, EntryClass::Group.to_value()),
3385 (Attribute::Name, Value::new_iname("testgroup")),
3386 (Attribute::Uuid, Value::Uuid(group_uuid))
3387 );
3388
3389 assert!(server_txn.internal_create(vec![e1]).is_ok());
3390 assert!(server_txn.commit().is_ok());
3391
3392 let mut server_txn = server.read().await.unwrap();
3394
3395 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3396 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3397
3398 let filter = ScimFilter::And(
3399 Box::new(ScimFilter::Equal(
3400 Attribute::Class.into(),
3401 EntryClass::Group.into(),
3402 )),
3403 Box::new(ScimFilter::Equal(
3404 Attribute::Uuid.into(),
3405 JsonValue::String(group_uuid.to_string()),
3406 )),
3407 );
3408
3409 let base: ScimListResponse = server_txn
3410 .scim_search_ext(idm_admin_ident, filter, ScimEntryGetQuery::default())
3411 .unwrap();
3412
3413 assert_eq!(base.resources.len(), 1);
3414 assert_eq!(base.total_results, 1);
3415 assert_eq!(base.items_per_page, None);
3417 assert_eq!(base.start_index, None);
3418 assert_eq!(base.resources[0].header.id, group_uuid);
3419 }
3420
3421 #[qs_test]
3422 async fn test_scim_basic_search_ext_query_with_sort(server: &QueryServer) {
3423 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3424
3425 for i in (1..4).rev() {
3426 let e1 = entry_init!(
3427 (Attribute::Class, EntryClass::Object.to_value()),
3428 (Attribute::Class, EntryClass::Group.to_value()),
3429 (
3430 Attribute::Name,
3431 Value::new_iname(format!("testgroup{i}").as_str())
3432 )
3433 );
3434 assert!(server_txn.internal_create(vec![e1]).is_ok());
3435 }
3436
3437 assert!(server_txn.commit().is_ok());
3438
3439 let mut server_txn = server.read().await.unwrap();
3441
3442 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3443 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3444
3445 let filter = ScimFilter::And(
3446 Box::new(ScimFilter::Equal(
3447 Attribute::Class.into(),
3448 EntryClass::Group.into(),
3449 )),
3450 Box::new(ScimFilter::StartsWith(
3451 Attribute::Name.into(),
3452 JsonValue::String("testgroup".into()),
3453 )),
3454 );
3455
3456 let base: ScimListResponse = server_txn
3457 .scim_search_ext(
3458 idm_admin_ident.clone(),
3459 filter.clone(),
3460 ScimEntryGetQuery {
3461 sort_by: Some(Attribute::Name),
3462 ..Default::default()
3463 },
3464 )
3465 .unwrap();
3466
3467 assert_eq!(base.resources.len(), 3);
3468 assert_eq!(base.total_results, 3);
3469 assert_eq!(base.items_per_page, None);
3471 assert_eq!(base.start_index, None);
3472
3473 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3474 base.resources[0].attrs.get(&Attribute::Name)
3475 else {
3476 panic!("Invalid data in attribute.");
3477 };
3478 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3479 base.resources[1].attrs.get(&Attribute::Name)
3480 else {
3481 panic!("Invalid data in attribute.");
3482 };
3483 let Some(ScimValueKanidm::String(testgroup_name_2)) =
3484 base.resources[2].attrs.get(&Attribute::Name)
3485 else {
3486 panic!("Invalid data in attribute.");
3487 };
3488
3489 assert!(testgroup_name_0 < testgroup_name_1);
3490 assert!(testgroup_name_0 < testgroup_name_2);
3491 assert!(testgroup_name_1 < testgroup_name_2);
3492
3493 let base: ScimListResponse = server_txn
3496 .scim_search_ext(
3497 idm_admin_ident.clone(),
3498 filter.clone(),
3499 ScimEntryGetQuery {
3500 count: NonZeroU64::new(1),
3501 ..Default::default()
3502 },
3503 )
3504 .unwrap();
3505
3506 assert_eq!(base.resources.len(), 1);
3507 assert_eq!(base.total_results, 3);
3508 assert_eq!(base.items_per_page, NonZeroU64::new(1));
3510 assert_eq!(base.start_index, NonZeroU64::new(1));
3511
3512 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3513 base.resources[0].attrs.get(&Attribute::Name)
3514 else {
3515 panic!("Invalid data in attribute.");
3516 };
3517 assert_eq!(testgroup_name_0, "testgroup3");
3519
3520 let base: ScimListResponse = server_txn
3523 .scim_search_ext(
3524 idm_admin_ident,
3525 filter.clone(),
3526 ScimEntryGetQuery {
3527 sort_by: Some(Attribute::Name),
3528 count: NonZeroU64::new(2),
3529 start_index: NonZeroU64::new(2),
3530 ..Default::default()
3531 },
3532 )
3533 .unwrap();
3534
3535 assert_eq!(base.resources.len(), 2);
3536 assert_eq!(base.total_results, 3);
3537 assert_eq!(base.items_per_page, NonZeroU64::new(2));
3538 assert_eq!(base.start_index, NonZeroU64::new(2));
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 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3546 base.resources[1].attrs.get(&Attribute::Name)
3547 else {
3548 panic!("Invalid data in attribute.");
3549 };
3550 assert_eq!(testgroup_name_0, "testgroup2");
3552 assert_eq!(testgroup_name_1, "testgroup3");
3553 }
3554}