Skip to main content

kanidmd_lib/server/
mod.rs

1//! `server` contains the query server, which is the main high level construction
2//! to coordinate queries and operations in the server.
3
4use self::access::{
5    profiles::{
6        AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
7    },
8    AccessControls, AccessControlsReadTransaction, AccessControlsTransaction,
9    AccessControlsWriteTransaction,
10};
11use self::keys::{
12    KeyObject, KeyProvider, KeyProviders, KeyProvidersReadTransaction, KeyProvidersTransaction,
13    KeyProvidersWriteTransaction,
14};
15use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction};
16use crate::filter::{
17    Filter, FilterInvalid, FilterValid, FilterValidResolved, ResolveFilterCache,
18    ResolveFilterCacheReadTxn,
19};
20use crate::plugins::{
21    self,
22    dyngroup::{DynGroup, DynGroupCache},
23    Plugins,
24};
25use crate::prelude::*;
26use crate::repl::cid::Cid;
27use crate::repl::proto::ReplRuvRange;
28use crate::repl::ruv::ReplicationUpdateVectorTransaction;
29use crate::schema::{
30    Schema, SchemaAttribute, SchemaClass, SchemaReadTransaction, SchemaTransaction,
31    SchemaWriteTransaction,
32};
33use crate::value::{CredentialType, EXTRACT_VAL_DN};
34use crate::valueset::*;
35use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
36use concread::cowcell::*;
37use crypto_glue::{hmac_s256::HmacSha256Key, s256::Sha256Output};
38use hashbrown::{HashMap, HashSet};
39use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
40use kanidm_proto::scim_v1::{
41    server::{ScimListResponse, ScimOAuth2ClaimMap, ScimOAuth2ScopeMap, ScimReference},
42    JsonValue, ScimEntryGetQuery, ScimFilter,
43};
44use std::collections::{BTreeMap, BTreeSet};
45use std::num::NonZeroU64;
46use std::str::FromStr;
47use std::sync::Arc;
48use time::OffsetDateTime;
49use tokio::sync::{Semaphore, SemaphorePermit};
50use tracing::trace;
51
52pub(crate) mod access;
53pub mod assert;
54pub mod batch_modify;
55pub mod create;
56pub mod delete;
57pub mod identity;
58pub(crate) mod keys;
59pub(crate) mod migrations;
60pub mod modify;
61pub(crate) mod recycle;
62pub mod scim;
63pub(crate) mod utils;
64
65const RESOLVE_FILTER_CACHE_MAX: usize = 256;
66const RESOLVE_FILTER_CACHE_LOCAL: usize = 8;
67
68#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq)]
69pub(crate) enum ServerPhase {
70    Bootstrap,
71    SchemaReady,
72    DomainInfoReady,
73    Running,
74}
75
76/// Domain Information. This should not contain sensitive information, the data within
77/// this structure may be used for public presentation.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct DomainInfo {
80    pub(crate) d_uuid: Uuid,
81    pub(crate) d_name: String,
82    pub(crate) d_display: String,
83    pub(crate) d_vers: DomainVersion,
84    pub(crate) d_patch_level: u32,
85    pub(crate) d_devel_taint: bool,
86    pub(crate) d_ldap_allow_unix_pw_bind: bool,
87    pub(crate) d_allow_easter_eggs: bool,
88    pub(crate) d_allow_account_recovery: bool,
89    // In future this should be image reference instead of the image itself.
90    d_image: Option<ImageValue>,
91}
92
93impl DomainInfo {
94    pub fn name(&self) -> &str {
95        self.d_name.as_str()
96    }
97
98    pub fn display_name(&self) -> &str {
99        self.d_display.as_str()
100    }
101
102    pub fn devel_taint(&self) -> bool {
103        self.d_devel_taint
104    }
105
106    pub fn image(&self) -> Option<&ImageValue> {
107        self.d_image.as_ref()
108    }
109
110    pub fn has_custom_image(&self) -> bool {
111        self.d_image.is_some()
112    }
113
114    pub fn allow_easter_eggs(&self) -> bool {
115        self.d_allow_easter_eggs
116    }
117
118    pub fn allow_account_recovery(&self) -> bool {
119        self.d_allow_account_recovery
120    }
121
122    #[cfg(feature = "test")]
123    pub fn new_test() -> CowCell<Self> {
124        concread::cowcell::CowCell::new(Self {
125            d_uuid: Uuid::new_v4(),
126            d_name: "test domain".to_string(),
127            d_display: "Test Domain".to_string(),
128            d_vers: 1,
129            d_patch_level: 0,
130            d_devel_taint: false,
131            d_ldap_allow_unix_pw_bind: false,
132            d_allow_easter_eggs: false,
133            d_allow_account_recovery: false,
134            d_image: None,
135        })
136    }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Default)]
140pub struct SystemConfig {
141    pub(crate) denied_names: HashSet<String>,
142    pub(crate) pw_badlist: HashSet<String>,
143}
144
145#[derive(Clone, Default)]
146pub struct HmacNameHistoryConfig {
147    pub(crate) enabled: bool,
148    pub(crate) key: HmacSha256Key,
149}
150
151#[derive(Clone, Default)]
152pub struct FeatureConfig {
153    pub(crate) hmac_name_history: HmacNameHistoryConfig,
154}
155
156#[derive(Clone)]
157pub struct QueryServer {
158    phase: Arc<CowCell<ServerPhase>>,
159    pub(crate) d_info: Arc<CowCell<DomainInfo>>,
160    system_config: Arc<CowCell<SystemConfig>>,
161    feature_config: Arc<CowCell<FeatureConfig>>,
162    be: Backend,
163    schema: Arc<Schema>,
164    accesscontrols: Arc<AccessControls>,
165    db_tickets: Arc<Semaphore>,
166    read_tickets: Arc<Semaphore>,
167    write_ticket: Arc<Semaphore>,
168    resolve_filter_cache: Arc<ResolveFilterCache>,
169    dyngroup_cache: Arc<CowCell<DynGroupCache>>,
170    cid_max: Arc<CowCell<Cid>>,
171    key_providers: Arc<KeyProviders>,
172}
173
174pub struct QueryServerReadTransaction<'a> {
175    be_txn: BackendReadTransaction<'a>,
176    // Anything else? In the future, we'll need to have a schema transaction
177    // type, maybe others?
178    pub(crate) d_info: CowCellReadTxn<DomainInfo>,
179    system_config: CowCellReadTxn<SystemConfig>,
180    feature_config: CowCellReadTxn<FeatureConfig>,
181    schema: SchemaReadTransaction,
182    accesscontrols: AccessControlsReadTransaction<'a>,
183    key_providers: KeyProvidersReadTransaction,
184    _db_ticket: SemaphorePermit<'a>,
185    _read_ticket: SemaphorePermit<'a>,
186    resolve_filter_cache: ResolveFilterCacheReadTxn<'a>,
187    // Future we may need this.
188    // cid_max: CowCellReadTxn<Cid>,
189    trim_cid: Cid,
190    txn_name_to_uuid: BTreeMap<String, Uuid>,
191}
192
193unsafe impl Sync for QueryServerReadTransaction<'_> {}
194
195unsafe impl Send for QueryServerReadTransaction<'_> {}
196
197bitflags::bitflags! {
198    #[derive(Copy, Clone, Debug)]
199    pub struct ChangeFlag: u64 {
200        const SCHEMA =                      0b0000_0000_0000_0001;
201        const ACP =                         0b0000_0000_0000_0010;
202        const OAUTH2 =                      0b0000_0000_0000_0100;
203        const DOMAIN =                      0b0000_0000_0000_1000;
204        const SYSTEM_CONFIG =               0b0000_0000_0001_0000;
205        const SYNC_AGREEMENT =              0b0000_0000_0010_0000;
206        const KEY_MATERIAL   =              0b0000_0000_0100_0000;
207        const APPLICATION    =              0b0000_0000_1000_0000;
208        const OAUTH2_CLIENT            =    0b0000_0001_0000_0000;
209        const FEATURE                  =    0b0000_0010_0000_0000;
210    }
211}
212
213pub struct QueryServerWriteTransaction<'a> {
214    committed: bool,
215    phase: CowCellWriteTxn<'a, ServerPhase>,
216    d_info: CowCellWriteTxn<'a, DomainInfo>,
217    system_config: CowCellWriteTxn<'a, SystemConfig>,
218    feature_config: CowCellWriteTxn<'a, FeatureConfig>,
219    curtime: Duration,
220    cid: CowCellWriteTxn<'a, Cid>,
221    trim_cid: Cid,
222    pub(crate) be_txn: BackendWriteTransaction<'a>,
223    pub(crate) schema: SchemaWriteTransaction<'a>,
224    accesscontrols: AccessControlsWriteTransaction<'a>,
225    key_providers: KeyProvidersWriteTransaction<'a>,
226    // We store a set of flags that indicate we need a reload of
227    // schema or acp, which is tested by checking the classes of the
228    // changing content.
229    pub(super) changed_flags: ChangeFlag,
230
231    // Store the list of changed uuids for other invalidation needs?
232    pub(super) changed_uuid: HashSet<Uuid>,
233    _db_ticket: SemaphorePermit<'a>,
234    _write_ticket: SemaphorePermit<'a>,
235    resolve_filter_cache_clear: bool,
236    resolve_filter_cache_write: ARCacheWriteTxn<
237        'a,
238        (IdentityId, Arc<Filter<FilterValid>>),
239        Arc<Filter<FilterValidResolved>>,
240        (),
241    >,
242    resolve_filter_cache: ARCacheReadTxn<
243        'a,
244        (IdentityId, Arc<Filter<FilterValid>>),
245        Arc<Filter<FilterValidResolved>>,
246        (),
247    >,
248    dyngroup_cache: CowCellWriteTxn<'a, DynGroupCache>,
249    txn_name_to_uuid: BTreeMap<String, Uuid>,
250}
251
252impl QueryServerWriteTransaction<'_> {
253    pub(crate) fn trim_cid(&self) -> &Cid {
254        &self.trim_cid
255    }
256}
257
258/// The `QueryServerTransaction` trait provides a set of common read only operations to be
259/// shared between [`QueryServerReadTransaction`] and [`QueryServerWriteTransaction`]s.
260///
261/// These operations tend to be high level constructions, generally different types of searches
262/// that are capable of taking different types of parameters and applying access controls or not,
263/// impersonating accounts, or bypassing these via internal searches.
264///
265/// [`QueryServerReadTransaction`]: struct.QueryServerReadTransaction.html
266/// [`QueryServerWriteTransaction`]: struct.QueryServerWriteTransaction.html
267pub trait QueryServerTransaction<'a> {
268    type BackendTransactionType: BackendTransaction;
269    fn get_be_txn(&mut self) -> &mut Self::BackendTransactionType;
270
271    type SchemaTransactionType: SchemaTransaction;
272    fn get_schema<'b>(&self) -> &'b Self::SchemaTransactionType;
273
274    type AccessControlsTransactionType: AccessControlsTransaction<'a>;
275    fn get_accesscontrols(&self) -> &Self::AccessControlsTransactionType;
276
277    type KeyProvidersTransactionType: KeyProvidersTransaction;
278    fn get_key_providers(&self) -> &Self::KeyProvidersTransactionType;
279
280    fn pw_badlist(&self) -> &HashSet<String>;
281
282    fn denied_names(&self) -> &HashSet<String>;
283
284    fn domain_info(&self) -> &DomainInfo;
285
286    fn get_domain_version(&self) -> DomainVersion;
287
288    fn get_domain_patch_level(&self) -> u32;
289
290    fn get_domain_development_taint(&self) -> bool;
291
292    fn get_domain_uuid(&self) -> Uuid;
293
294    fn get_domain_name(&self) -> &str;
295
296    fn get_domain_display_name(&self) -> &str;
297
298    fn get_domain_image_value(&self) -> Option<ImageValue>;
299
300    fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>>;
301
302    fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig;
303
304    fn txn_name_to_uuid(&mut self) -> &mut BTreeMap<String, Uuid>;
305
306    // Because of how borrowck in rust works, if we need to get two inner types we have to get them
307    // in a single fn.
308
309    fn get_resolve_filter_cache_and_be_txn(
310        &mut self,
311    ) -> (
312        &mut Self::BackendTransactionType,
313        Option<&mut ResolveFilterCacheReadTxn<'a>>,
314    );
315
316    /// Conduct a search and apply access controls to yield a set of entries that
317    /// have been reduced to the set of user visible avas. Note that if you provide
318    /// a `SearchEvent` for the internal user, this query will fail. It is invalid for
319    /// the [`access`] module to attempt to reduce avas for internal searches, and you
320    /// should use [`fn search`] instead.
321    ///
322    /// [`SearchEvent`]: ../event/struct.SearchEvent.html
323    /// [`access`]: ../access/index.html
324    /// [`fn search`]: trait.QueryServerTransaction.html#method.search
325    #[instrument(level = "debug", skip_all)]
326    fn search_ext(
327        &mut self,
328        se: &SearchEvent,
329    ) -> Result<Vec<EntryReducedCommitted>, OperationError> {
330        /*
331         * This just wraps search, but it's for the external interface
332         * so as a result it also reduces the entry set's attributes at
333         * the end.
334         */
335        let entries = self.search(se)?;
336
337        let access = self.get_accesscontrols();
338        access
339            .search_filter_entry_attributes(se, entries)
340            .map_err(|e| {
341                // Log and fail if something went wrong.
342                admin_error!(?e, "Failed to filter entry attributes");
343                e
344            })
345        // This now returns the reduced vec.
346    }
347
348    #[instrument(level = "debug", skip_all)]
349    fn search(
350        &mut self,
351        se: &SearchEvent,
352    ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
353        if se.ident.is_internal() {
354            trace!(internal_filter = ?se.filter, "search");
355        } else {
356            security_info!(initiator = %se.ident, "search");
357            admin_debug!(external_filter = ?se.filter, "search");
358        }
359
360        // This is an important security step because it prevents us from
361        // performing un-indexed searches on attr's that don't exist in the
362        // server. This is why ExtensibleObject can only take schema that
363        // exists in the server, not arbitrary attr names.
364        //
365        // This normalises and validates in a single step.
366        //
367        // NOTE: Filters are validated in event conversion.
368
369        let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
370
371        let idxmeta = be_txn.get_idxmeta_ref();
372
373        trace!(resolve_filter_cache = %resolve_filter_cache.is_some());
374
375        // Now resolve all references and indexes.
376        let vfr = se
377            .filter
378            .resolve(&se.ident, Some(idxmeta), resolve_filter_cache)
379            .map_err(|e| {
380                admin_error!(?e, "search filter resolve failure");
381                e
382            })?;
383
384        let lims = se.ident.limits();
385
386        // NOTE: We currently can't build search plugins due to the inability to hand
387        // the QS wr/ro to the plugin trait. However, there shouldn't be a need for search
388        // plugins, because all data transforms should be in the write path.
389
390        let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
391            admin_error!(?e, "backend failure");
392            OperationError::Backend
393        })?;
394
395        // Apply ACP before we let the plugins "have at it".
396        // WARNING; for external searches this is NOT the only
397        // ACP application. There is a second application to reduce the
398        // attribute set on the entries!
399        //
400        let access = self.get_accesscontrols();
401        access.search_filter_entries(se, res).map_err(|e| {
402            admin_error!(?e, "Unable to access filter entries");
403            e
404        })
405    }
406
407    #[instrument(level = "debug", skip_all)]
408    fn exists(&mut self, ee: &ExistsEvent) -> Result<bool, OperationError> {
409        let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
410        let idxmeta = be_txn.get_idxmeta_ref();
411
412        let vfr = ee
413            .filter
414            .resolve(&ee.ident, Some(idxmeta), resolve_filter_cache)
415            .map_err(|e| {
416                admin_error!(?e, "Failed to resolve filter");
417                e
418            })?;
419
420        let lims = ee.ident.limits();
421
422        if ee.ident.is_internal() {
423            // We take a fast-path on internal because we can skip loading entries
424            // at all in this case.
425            be_txn.exists(lims, &vfr).map_err(|e| {
426                admin_error!(?e, "backend failure");
427                OperationError::Backend
428            })
429        } else {
430            // For external idents, we need to load the entries else we can't apply
431            // access controls to them.
432            let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
433                admin_error!(?e, "backend failure");
434                OperationError::Backend
435            })?;
436
437            // ⚠️  Compare / Exists is annoying security wise. It has the
438            // capability to easily leak information based on comparisons
439            // that have been made. In the external account case, we need
440            // to filter entries as a result.
441
442            // Apply ACP before we return the bool state.
443            let access = self.get_accesscontrols();
444            access
445                .filter_entries(&ee.ident, &ee.filter_orig, res)
446                .map_err(|e| {
447                    admin_error!(?e, "Unable to access filter entries");
448                    e
449                })
450                .map(|entries| !entries.is_empty())
451        }
452    }
453
454    fn name_to_uuid(&mut self, name: &str) -> Result<Uuid, OperationError> {
455        // There are some contexts where we will be passed an rdn or dn. We need
456        // to remove these elements if they exist.
457        //
458        // Why is it okay to ignore the attr and dn here? In Kani spn and name are
459        // always unique and absolutes, so even if the dn/rdn are not expected, there
460        // is only a single correct answer that *can* match these values. This also
461        // hugely simplifies the process of matching when we have app based searches
462        // in future too.
463        let work = EXTRACT_VAL_DN
464            .captures(name)
465            .and_then(|caps| caps.name("val"))
466            .map(|v| v.as_str().to_lowercase())
467            .ok_or(OperationError::InvalidValueState)?;
468
469        // Is it just a uuid?
470        if let Ok(uuid) = Uuid::parse_str(&work) {
471            return Ok(uuid);
472        }
473
474        if let Some(uuid) = self.get_be_txn().name2uuid(&work)? {
475            return Ok(uuid);
476        }
477
478        if let Some(uuid) = self.txn_name_to_uuid().get(name) {
479            Ok(*uuid)
480        } else {
481            Err(OperationError::NoMatchingEntries)
482        }
483    }
484
485    // Similar to name, but where we lookup from external_id instead.
486    fn sync_external_id_to_uuid(
487        &mut self,
488        external_id: &str,
489    ) -> Result<Option<Uuid>, OperationError> {
490        // Is it just a uuid?
491        Uuid::parse_str(external_id).map(Some).or_else(|_| {
492            let lname = external_id.to_lowercase();
493            self.get_be_txn().externalid2uuid(lname.as_str())
494        })
495    }
496
497    fn uuid_to_spn(&mut self, uuid: Uuid) -> Result<Option<Value>, OperationError> {
498        let r = self.get_be_txn().uuid2spn(uuid)?;
499
500        if let Some(ref n) = r {
501            // Shouldn't we be doing more graceful error handling here?
502            // Or, if we know it will always be true, we should remove this.
503            debug_assert!(n.is_spn() || n.is_iname());
504        }
505
506        Ok(r)
507    }
508
509    fn uuid_to_rdn(&mut self, uuid: Uuid) -> Result<String, OperationError> {
510        // If we have a some, pass it on, else unwrap into a default.
511        self.get_be_txn()
512            .uuid2rdn(uuid)
513            .map(|v| v.unwrap_or_else(|| format!("uuid={}", uuid.as_hyphenated())))
514    }
515
516    /// From internal, generate an "exists" event and dispatch
517    #[instrument(level = "debug", skip_all)]
518    fn internal_exists(&mut self, filter: &Filter<FilterInvalid>) -> Result<bool, OperationError> {
519        // Check the filter
520        let f_valid = filter
521            .validate(self.get_schema())
522            .map_err(OperationError::SchemaViolation)?;
523        // Build an exists event
524        let ee = ExistsEvent::new_internal(f_valid);
525        // Submit it
526        self.exists(&ee)
527    }
528
529    #[instrument(level = "debug", skip_all)]
530    fn internal_exists_uuid(&mut self, uuid: Uuid) -> Result<bool, OperationError> {
531        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
532        self.internal_exists(&filter)
533    }
534
535    #[instrument(level = "debug", skip_all)]
536    fn internal_search(
537        &mut self,
538        filter: Filter<FilterInvalid>,
539    ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
540        let f_valid = filter
541            .validate(self.get_schema())
542            .map_err(OperationError::SchemaViolation)?;
543        let se = SearchEvent::new_internal(f_valid);
544        self.search(&se)
545    }
546
547    #[instrument(level = "debug", skip_all)]
548    fn impersonate_search_valid(
549        &mut self,
550        f_valid: Filter<FilterValid>,
551        f_intent_valid: Filter<FilterValid>,
552        event: &Identity,
553    ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
554        let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
555        self.search(&se)
556    }
557
558    /// Applies ACP to filter result entries.
559    fn impersonate_search_ext_valid(
560        &mut self,
561        f_valid: Filter<FilterValid>,
562        f_intent_valid: Filter<FilterValid>,
563        event: &Identity,
564    ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
565        let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
566        self.search_ext(&se)
567    }
568
569    // Who they are will go here
570    fn impersonate_search(
571        &mut self,
572        filter: Filter<FilterInvalid>,
573        filter_intent: Filter<FilterInvalid>,
574        event: &Identity,
575    ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
576        let f_valid = filter
577            .validate(self.get_schema())
578            .map_err(OperationError::SchemaViolation)?;
579        let f_intent_valid = filter_intent
580            .validate(self.get_schema())
581            .map_err(OperationError::SchemaViolation)?;
582        self.impersonate_search_valid(f_valid, f_intent_valid, event)
583    }
584
585    #[instrument(level = "debug", skip_all)]
586    fn impersonate_search_ext(
587        &mut self,
588        filter: Filter<FilterInvalid>,
589        filter_intent: Filter<FilterInvalid>,
590        event: &Identity,
591    ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
592        let f_valid = filter
593            .validate(self.get_schema())
594            .map_err(OperationError::SchemaViolation)?;
595        let f_intent_valid = filter_intent
596            .validate(self.get_schema())
597            .map_err(OperationError::SchemaViolation)?;
598        self.impersonate_search_ext_valid(f_valid, f_intent_valid, event)
599    }
600
601    /// Get a single entry by its UUID. This is used heavily for internal
602    /// server operations, especially in login and ACP checks.
603    #[instrument(level = "debug", skip_all)]
604    fn internal_search_uuid(
605        &mut self,
606        uuid: Uuid,
607    ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
608        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
609        let f_valid = filter.validate(self.get_schema()).map_err(|e| {
610            error!(?e, "Filter Validate - SchemaViolation");
611            OperationError::SchemaViolation(e)
612        })?;
613        let se = SearchEvent::new_internal(f_valid);
614
615        let mut vs = self.search(&se)?;
616        match vs.pop() {
617            Some(entry) if vs.is_empty() => Ok(entry),
618            _ => Err(OperationError::NoMatchingEntries),
619        }
620    }
621
622    /// Get a single entry by its UUID, even if the entry in question
623    /// is in a masked state (recycled, tombstoned).
624    #[instrument(level = "debug", skip_all)]
625    fn internal_search_all_uuid(
626        &mut self,
627        uuid: Uuid,
628    ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
629        let filter = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
630        let f_valid = filter.validate(self.get_schema()).map_err(|e| {
631            error!(?e, "Filter Validate - SchemaViolation");
632            OperationError::SchemaViolation(e)
633        })?;
634        let se = SearchEvent::new_internal(f_valid);
635
636        let mut vs = self.search(&se)?;
637        match vs.pop() {
638            Some(entry) if vs.is_empty() => Ok(entry),
639            _ => Err(OperationError::NoMatchingEntries),
640        }
641    }
642
643    /// Get all conflict entries that originated from a source uuid.
644    #[instrument(level = "debug", skip_all)]
645    fn internal_search_conflict_uuid(
646        &mut self,
647        uuid: Uuid,
648    ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
649        let filter = filter_all!(f_and(vec![
650            f_eq(Attribute::SourceUuid, PartialValue::Uuid(uuid)),
651            f_eq(Attribute::Class, EntryClass::Conflict.into())
652        ]));
653        let f_valid = filter.validate(self.get_schema()).map_err(|e| {
654            error!(?e, "Filter Validate - SchemaViolation");
655            OperationError::SchemaViolation(e)
656        })?;
657        let se = SearchEvent::new_internal(f_valid);
658
659        self.search(&se)
660    }
661
662    #[instrument(level = "debug", skip_all)]
663    fn impersonate_search_ext_uuid(
664        &mut self,
665        uuid: Uuid,
666        event: &Identity,
667    ) -> Result<Entry<EntryReduced, EntryCommitted>, OperationError> {
668        let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
669        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
670
671        let mut vs = self.impersonate_search_ext(filter, filter_intent, event)?;
672        match vs.pop() {
673            Some(entry) if vs.is_empty() => Ok(entry),
674            _ => {
675                if vs.is_empty() {
676                    Err(OperationError::NoMatchingEntries)
677                } else {
678                    // Multiple entries matched, should not be possible!
679                    Err(OperationError::UniqueConstraintViolation)
680                }
681            }
682        }
683    }
684
685    #[instrument(level = "debug", skip_all)]
686    fn impersonate_search_uuid(
687        &mut self,
688        uuid: Uuid,
689        event: &Identity,
690    ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
691        let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
692        let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
693
694        let mut vs = self.impersonate_search(filter, filter_intent, event)?;
695        match vs.pop() {
696            Some(entry) if vs.is_empty() => Ok(entry),
697            _ => Err(OperationError::NoMatchingEntries),
698        }
699    }
700
701    /// Given an entries UUID, return an impersonation session for that account. This
702    /// should be used with care, as it allows you to perform actions internally on
703    /// behalf of a another user, without checking the permissions of the original
704    /// user.
705    fn impersonate_uuid_as_readwrite_identity(
706        &mut self,
707        uuid: Uuid,
708    ) -> Result<Identity, OperationError> {
709        self.internal_search_uuid(uuid)
710            .map(Identity::from_impersonate_entry_readwrite)
711    }
712
713    /// Do a schema aware conversion from a String:String to String:Value for modification
714    /// present.
715    fn clone_value(&mut self, attr: &Attribute, value: &str) -> Result<Value, OperationError> {
716        let schema = self.get_schema();
717
718        // Should this actually be a fn of Value - no - I think that introduces issues with the
719        // monomorphisation of the trait for transactions, so we should have this here.
720
721        // Lookup the attr
722        match schema.get_attributes().get(attr) {
723            Some(schema_a) => {
724                match schema_a.syntax {
725                    SyntaxType::Utf8String => Ok(Value::new_utf8(value.to_string())),
726                    SyntaxType::Utf8StringInsensitive => Ok(Value::new_iutf8(value)),
727                    SyntaxType::Utf8StringIname => Ok(Value::new_iname(value)),
728                    SyntaxType::Boolean => Value::new_bools(value)
729                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
730                    SyntaxType::SyntaxId => Value::new_syntaxs(value)
731                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
732                    SyntaxType::IndexId => Value::new_indexes(value)
733                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
734                    SyntaxType::CredentialType => CredentialType::try_from(value)
735                        .map(Value::CredentialType)
736                        .map_err(|()| OperationError::InvalidAttribute("Invalid CredentialType syntax".to_string())),
737                    SyntaxType::Uuid => {
738                        // Attempt to resolve this name to a uuid. If it's already a uuid, then
739                        // name to uuid will "do the right thing" and give us the Uuid back.
740                        let un = self
741                            .name_to_uuid(value)
742                            .unwrap_or(UUID_DOES_NOT_EXIST);
743                        Ok(Value::Uuid(un))
744                    }
745                    SyntaxType::ReferenceUuid => {
746                        let un = self
747                            .name_to_uuid(value)
748                            .unwrap_or(UUID_DOES_NOT_EXIST);
749                        Ok(Value::Refer(un))
750                    }
751                    SyntaxType::JsonFilter => Value::new_json_filter_s(value)
752                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
753                    SyntaxType::Image => Value::new_image(value),
754
755                    SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
756                    SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
757                    SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
758                    SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
759                    SyntaxType::Uint32 => Value::new_uint32_str(value)
760                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
761                    SyntaxType::Int64 => Value::new_int64_str(value)
762                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid int64 syntax".to_string())),
763                    SyntaxType::Uint64 => Value::new_uint64_str(value)
764                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid uint64 syntax".to_string())),
765                    SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
766                    SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
767                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid NsUniqueId syntax".to_string())),
768                    SyntaxType::DateTime => Value::new_datetime_s(value)
769                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
770                    SyntaxType::EmailAddress => Value::new_email_address_s(value)
771                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Email Address syntax".to_string())),
772                    SyntaxType::Url => Value::new_url_s(value)
773                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Url (whatwg/url) syntax".to_string())),
774                    SyntaxType::OauthScope => Value::new_oauthscope(value)
775                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Oauth Scope syntax".to_string())),
776                    SyntaxType::WebauthnAttestationCaList => Value::new_webauthn_attestation_ca_list(value)
777                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid Webauthn Attestation CA List".to_string())),
778                    SyntaxType::OauthScopeMap => Err(OperationError::InvalidAttribute("Oauth Scope Maps can not be supplied through modification - please use the IDM api".to_string())),
779                    SyntaxType::OauthClaimMap => Err(OperationError::InvalidAttribute("Oauth Claim Maps can not be supplied through modification - please use the IDM api".to_string())),
780                    SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute("Private Binary Values can not be supplied through modification".to_string())),
781                    SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
782                    SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
783                    SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute("AttestedPasskey Values can not be supplied through modification".to_string())),
784                    SyntaxType::Session => Err(OperationError::InvalidAttribute("Session Values can not be supplied through modification".to_string())),
785                    SyntaxType::ApiToken => Err(OperationError::InvalidAttribute("ApiToken Values can not be supplied through modification".to_string())),
786                    SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())),
787                    SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())),
788                    SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute("Oauth2Session Values can not be supplied through modification".to_string())),
789                    SyntaxType::UiHint => UiHint::from_str(value)
790                        .map(Value::UiHint)
791                        .map_err(|()| OperationError::InvalidAttribute("Invalid uihint syntax".to_string())),
792                    SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute("TotpSecret Values can not be supplied through modification".to_string())),
793                    SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute("Audit logs are generated and not able to be set.".to_string())),
794                    SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute("Ec keys are generated and not able to be set.".to_string())),
795                    SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute("Internal keys are generated and not able to be set.".to_string())),
796                    SyntaxType::HexString => Value::new_hex_string_s(value)
797                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid hex string syntax".to_string())),
798                    SyntaxType::Certificate => Value::new_certificate_s(value)
799                        .ok_or_else(|| OperationError::InvalidAttribute("Invalid x509 certificate syntax".to_string())),
800                    SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute("ApplicationPassword values can not be supplied through modification".to_string())),
801                    SyntaxType::Json => Err(OperationError::InvalidAttribute("Json values can not be supplied through modification".to_string())),
802                    SyntaxType::Sha256 => Err(OperationError::InvalidAttribute("SHA256 values can not be supplied through modification".to_string())),
803                    SyntaxType::Message => Err(OperationError::InvalidAttribute("Message values can not be supplied through modification".to_string())),
804                }
805            }
806            None => {
807                // No attribute of this name exists - fail fast, there is no point to
808                // proceed, as nothing can be satisfied.
809                Err(OperationError::InvalidAttributeName(attr.to_string()))
810            }
811        }
812    }
813
814    fn clone_partialvalue(
815        &mut self,
816        attr: &Attribute,
817        value: &str,
818    ) -> Result<PartialValue, OperationError> {
819        let schema = self.get_schema();
820
821        // Lookup the attr
822        match schema.get_attributes().get(attr) {
823            Some(schema_a) => {
824                match schema_a.syntax {
825                    SyntaxType::Utf8String | SyntaxType::TotpSecret => {
826                        Ok(PartialValue::new_utf8(value.to_string()))
827                    }
828                    SyntaxType::Utf8StringInsensitive
829                    | SyntaxType::JwsKeyEs256
830                    | SyntaxType::JwsKeyRs256 => Ok(PartialValue::new_iutf8(value)),
831                    SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
832                    SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
833                        OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
834                    }),
835                    SyntaxType::SyntaxId => PartialValue::new_syntaxs(value).ok_or_else(|| {
836                        OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
837                    }),
838                    SyntaxType::IndexId => PartialValue::new_indexes(value).ok_or_else(|| {
839                        OperationError::InvalidAttribute("Invalid Index syntax".to_string())
840                    }),
841                    SyntaxType::CredentialType => CredentialType::try_from(value)
842                        .map(PartialValue::CredentialType)
843                        .map_err(|()| {
844                            OperationError::InvalidAttribute(
845                                "Invalid credentialtype syntax".to_string(),
846                            )
847                        }),
848                    SyntaxType::Uuid => {
849                        let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
850                        Ok(PartialValue::Uuid(un))
851                    }
852                    // ⚠️   Any types here need to also be added to update_attributes in
853                    // schema.rs for reference type / cache awareness during referential
854                    // integrity processing. Exceptions are self-contained value types!
855                    SyntaxType::ReferenceUuid
856                    | SyntaxType::OauthScopeMap
857                    | SyntaxType::Session
858                    | SyntaxType::ApiToken
859                    | SyntaxType::Oauth2Session
860                    | SyntaxType::ApplicationPassword => {
861                        let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
862                        Ok(PartialValue::Refer(un))
863                    }
864                    SyntaxType::OauthClaimMap => self
865                        .name_to_uuid(value)
866                        .map(PartialValue::Refer)
867                        .or_else(|_| Ok(PartialValue::new_iutf8(value))),
868
869                    SyntaxType::JsonFilter => {
870                        PartialValue::new_json_filter_s(value).ok_or_else(|| {
871                            OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
872                        })
873                    }
874                    SyntaxType::Credential => Ok(PartialValue::new_credential_tag(value)),
875                    SyntaxType::SecretUtf8String => Ok(PartialValue::new_secret_str()),
876                    SyntaxType::SshKey => Ok(PartialValue::new_sshkey_tag_s(value)),
877                    SyntaxType::SecurityPrincipalName => {
878                        PartialValue::new_spn_s(value).ok_or_else(|| {
879                            OperationError::InvalidAttribute("Invalid spn syntax".to_string())
880                        })
881                    }
882                    SyntaxType::Uint32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
883                        OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
884                    }),
885                    SyntaxType::Uint64 => PartialValue::new_uint64_str(value).ok_or_else(|| {
886                        OperationError::InvalidAttribute("Invalid uint64 syntax".to_string())
887                    }),
888                    SyntaxType::Int64 => PartialValue::new_int64_str(value).ok_or_else(|| {
889                        OperationError::InvalidAttribute("Invalid int64 syntax".to_string())
890                    }),
891                    SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
892                        OperationError::InvalidAttribute("Invalid cid syntax".to_string())
893                    }),
894                    SyntaxType::NsUniqueId => Ok(PartialValue::new_nsuniqueid_s(value)),
895                    SyntaxType::DateTime => PartialValue::new_datetime_s(value).ok_or_else(|| {
896                        OperationError::InvalidAttribute(
897                            "Invalid DateTime (rfc3339) syntax".to_string(),
898                        )
899                    }),
900                    SyntaxType::EmailAddress => Ok(PartialValue::new_email_address_s(value)),
901                    SyntaxType::Url => PartialValue::new_url_s(value).ok_or_else(|| {
902                        OperationError::InvalidAttribute(
903                            "Invalid Url (whatwg/url) syntax".to_string(),
904                        )
905                    }),
906                    SyntaxType::OauthScope => Ok(PartialValue::new_oauthscope(value)),
907                    SyntaxType::PrivateBinary => Ok(PartialValue::PrivateBinary),
908                    SyntaxType::IntentToken => PartialValue::new_intenttoken_s(value.to_string())
909                        .ok_or_else(|| {
910                            OperationError::InvalidAttribute(
911                                "Invalid Intent Token ID (uuid) syntax".to_string(),
912                            )
913                        }),
914                    SyntaxType::Passkey => PartialValue::new_passkey_s(value).ok_or_else(|| {
915                        OperationError::InvalidAttribute("Invalid Passkey UUID syntax".to_string())
916                    }),
917                    SyntaxType::AttestedPasskey => PartialValue::new_attested_passkey_s(value)
918                        .ok_or_else(|| {
919                            OperationError::InvalidAttribute(
920                                "Invalid AttestedPasskey UUID syntax".to_string(),
921                            )
922                        }),
923                    SyntaxType::UiHint => UiHint::from_str(value)
924                        .map(PartialValue::UiHint)
925                        .map_err(|()| {
926                            OperationError::InvalidAttribute("Invalid uihint syntax".to_string())
927                        }),
928                    SyntaxType::AuditLogString => Ok(PartialValue::new_utf8s(value)),
929                    SyntaxType::EcKeyPrivate => Ok(PartialValue::SecretValue),
930                    SyntaxType::Image => Ok(PartialValue::new_utf8s(value)),
931                    SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
932                        "Invalid - unable to query attestation CA list".to_string(),
933                    )),
934                    SyntaxType::HexString | SyntaxType::KeyInternal | SyntaxType::Certificate => {
935                        PartialValue::new_hex_string_s(value).ok_or_else(|| {
936                            OperationError::InvalidAttribute(
937                                "Invalid syntax, expected hex string".to_string(),
938                            )
939                        })
940                    }
941                    SyntaxType::Sha256 => {
942                        let mut sha256bytes = Sha256Output::default();
943                        if hex::decode_to_slice(value, &mut sha256bytes).is_ok() {
944                            Ok(PartialValue::Sha256(sha256bytes))
945                        } else {
946                            Err(OperationError::InvalidAttribute(
947                                "Invalid syntax, expected sha256 hex string containing 64 characters".to_string(),
948                            ))
949                        }
950                    }
951                    SyntaxType::Json => Err(OperationError::InvalidAttribute(
952                        "Json values can not be validated by this interface".to_string(),
953                    )),
954                    SyntaxType::Message => Err(OperationError::InvalidAttribute(
955                        "Message values can not be validated by this interface".to_string(),
956                    )),
957                }
958            }
959            None => {
960                // No attribute of this name exists - fail fast, there is no point to
961                // proceed, as nothing can be satisfied.
962                Err(OperationError::InvalidAttributeName(attr.to_string()))
963            }
964        }
965    }
966
967    fn resolve_scim_interim(
968        &mut self,
969        scim_value_intermediate: ScimValueIntermediate,
970    ) -> Result<Option<ScimValueKanidm>, OperationError> {
971        match scim_value_intermediate {
972            ScimValueIntermediate::References(uuids) => {
973                let scim_references = uuids
974                    .into_iter()
975                    .map(|uuid| {
976                        self.uuid_to_spn(uuid)
977                            .and_then(|maybe_value| {
978                                maybe_value.ok_or(OperationError::InvalidValueState)
979                            })
980                            .map(|value| ScimReference {
981                                uuid,
982                                value: value.to_proto_string_clone(),
983                            })
984                    })
985                    .collect::<Result<Vec<_>, _>>()?;
986                Ok(Some(ScimValueKanidm::EntryReferences(scim_references)))
987            }
988            ScimValueIntermediate::Oauth2ClaimMap(unresolved_maps) => {
989                let scim_claim_maps = unresolved_maps
990                    .into_iter()
991                    .map(
992                        |UnresolvedScimValueOauth2ClaimMap {
993                             group_uuid,
994                             claim,
995                             join_char,
996                             values,
997                         }| {
998                            self.uuid_to_spn(group_uuid)
999                                .and_then(|maybe_value| {
1000                                    maybe_value.ok_or(OperationError::InvalidValueState)
1001                                })
1002                                .map(|value| ScimOAuth2ClaimMap {
1003                                    group: value.to_proto_string_clone(),
1004                                    group_uuid,
1005                                    claim,
1006                                    join_char,
1007                                    values,
1008                                })
1009                        },
1010                    )
1011                    .collect::<Result<Vec<_>, _>>()?;
1012
1013                Ok(Some(ScimValueKanidm::OAuth2ClaimMap(scim_claim_maps)))
1014            }
1015
1016            ScimValueIntermediate::Oauth2ScopeMap(unresolved_maps) => {
1017                let scim_claim_maps = unresolved_maps
1018                    .into_iter()
1019                    .map(|UnresolvedScimValueOauth2ScopeMap { group_uuid, scopes }| {
1020                        self.uuid_to_spn(group_uuid)
1021                            .and_then(|maybe_value| {
1022                                maybe_value.ok_or(OperationError::InvalidValueState)
1023                            })
1024                            .map(|value| ScimOAuth2ScopeMap {
1025                                group: value.to_proto_string_clone(),
1026                                group_uuid,
1027                                scopes,
1028                            })
1029                    })
1030                    .collect::<Result<Vec<_>, _>>()?;
1031
1032                Ok(Some(ScimValueKanidm::OAuth2ScopeMap(scim_claim_maps)))
1033            }
1034        }
1035    }
1036
1037    fn resolve_scim_json_get(
1038        &mut self,
1039        attr: &Attribute,
1040        value: &JsonValue,
1041    ) -> Result<PartialValue, OperationError> {
1042        let schema = self.get_schema();
1043        // Lookup the attr
1044        let Some(schema_a) = schema.get_attributes().get(attr) else {
1045            // No attribute of this name exists - fail fast, there is no point to
1046            // proceed, as nothing can be satisfied.
1047            return Err(OperationError::InvalidAttributeName(attr.to_string()));
1048        };
1049
1050        debug!(schema_syntax = ?schema_a.syntax, ?value);
1051
1052        match schema_a.syntax {
1053            SyntaxType::Utf8String => {
1054                let JsonValue::String(value) = value else {
1055                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1056                };
1057                Ok(PartialValue::Utf8(value.to_string()))
1058            }
1059            SyntaxType::Utf8StringInsensitive => {
1060                let JsonValue::String(value) = value else {
1061                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1062                };
1063                Ok(PartialValue::new_iutf8(value))
1064            }
1065            SyntaxType::Utf8StringIname => {
1066                let JsonValue::String(value) = value else {
1067                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1068                };
1069                Ok(PartialValue::new_iname(value))
1070            }
1071            SyntaxType::Uuid => {
1072                let JsonValue::String(value) = value else {
1073                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1074                };
1075
1076                let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1077                Ok(PartialValue::Uuid(un))
1078            }
1079            SyntaxType::Boolean => {
1080                let JsonValue::Bool(value) = value else {
1081                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1082                };
1083                Ok(PartialValue::Bool(*value))
1084            }
1085            SyntaxType::SyntaxId => {
1086                let JsonValue::String(value) = value else {
1087                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1088                };
1089                let Ok(value) = SyntaxType::try_from(value.as_str()) else {
1090                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1091                };
1092                Ok(PartialValue::Syntax(value))
1093            }
1094            SyntaxType::ReferenceUuid
1095            | SyntaxType::OauthScopeMap
1096            | SyntaxType::Session
1097            | SyntaxType::ApiToken
1098            | SyntaxType::Oauth2Session
1099            | SyntaxType::ApplicationPassword => {
1100                let JsonValue::String(value) = value else {
1101                    return Err(OperationError::InvalidAttribute(attr.to_string()));
1102                };
1103
1104                let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1105                Ok(PartialValue::Refer(un))
1106            }
1107
1108            _ => Err(OperationError::InvalidAttribute(attr.to_string())),
1109        }
1110    }
1111
1112    fn resolve_valueset_intermediate(
1113        &mut self,
1114        vs_inter: ValueSetIntermediate,
1115    ) -> Result<ValueSet, OperationError> {
1116        match vs_inter {
1117            ValueSetIntermediate::References {
1118                mut resolved,
1119                unresolved,
1120            } => {
1121                for value in unresolved {
1122                    let un = self.name_to_uuid(value.as_str()).unwrap_or_else(|_| {
1123                        warn!(
1124                            ?value,
1125                            "Value can not be resolved to a uuid - assuming it does not exist."
1126                        );
1127                        UUID_DOES_NOT_EXIST
1128                    });
1129
1130                    resolved.insert(un);
1131                }
1132
1133                let vs = ValueSetRefer::from_set(resolved);
1134                Ok(vs)
1135            }
1136
1137            ValueSetIntermediate::Oauth2ClaimMap {
1138                mut resolved,
1139                unresolved,
1140            } => {
1141                resolved.extend(unresolved.into_iter().map(
1142                    |UnresolvedValueSetOauth2ClaimMap {
1143                         group_name,
1144                         claim,
1145                         join_char,
1146                         claim_values,
1147                     }| {
1148                        let group_uuid =
1149                            self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1150                                warn!(
1151                            ?group_name,
1152                            "Value can not be resolved to a uuid - assuming it does not exist."
1153                        );
1154                                UUID_DOES_NOT_EXIST
1155                            });
1156
1157                        ResolvedValueSetOauth2ClaimMap {
1158                            group_uuid,
1159                            claim,
1160                            join_char,
1161                            claim_values,
1162                        }
1163                    },
1164                ));
1165
1166                let vs = ValueSetOauthClaimMap::from_set(resolved);
1167                Ok(vs)
1168            }
1169
1170            ValueSetIntermediate::Oauth2ScopeMap {
1171                mut resolved,
1172                unresolved,
1173            } => {
1174                resolved.extend(unresolved.into_iter().map(
1175                    |UnresolvedValueSetOauth2ScopeMap { group_name, scopes }| {
1176                        let group_uuid =
1177                            self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1178                                warn!(
1179                            ?group_name,
1180                            "Value can not be resolved to a uuid - assuming it does not exist."
1181                        );
1182                                UUID_DOES_NOT_EXIST
1183                            });
1184
1185                        ResolvedValueSetOauth2ScopeMap { group_uuid, scopes }
1186                    },
1187                ));
1188
1189                let vs = ValueSetOauthScopeMap::from_set(resolved);
1190                Ok(vs)
1191            }
1192        }
1193    }
1194
1195    // In the opposite direction, we can resolve values for presentation
1196    fn resolve_valueset(&mut self, value: &ValueSet) -> Result<Vec<String>, OperationError> {
1197        if let Some(r_set) = value.as_refer_set() {
1198            let v: Result<Vec<_>, _> = r_set
1199                .iter()
1200                .copied()
1201                .map(|ur| {
1202                    let nv = self.uuid_to_spn(ur)?;
1203                    match nv {
1204                        Some(v) => Ok(v.to_proto_string_clone()),
1205                        None => Ok(uuid_to_proto_string(ur)),
1206                    }
1207                })
1208                .collect();
1209            v
1210        } else if let Some(r_map) = value.as_oauthscopemap() {
1211            let v: Result<Vec<_>, _> = r_map
1212                .iter()
1213                .map(|(u, m)| {
1214                    let nv = self.uuid_to_spn(*u)?;
1215                    let u = match nv {
1216                        Some(v) => v.to_proto_string_clone(),
1217                        None => uuid_to_proto_string(*u),
1218                    };
1219                    Ok(format!("{u}: {m:?}"))
1220                })
1221                .collect();
1222            v
1223        } else if let Some(r_map) = value.as_oauthclaim_map() {
1224            let mut v = Vec::with_capacity(0);
1225            for (claim_name, mapping) in r_map.iter() {
1226                for (group_ref, claims) in mapping.values() {
1227                    let join_char = mapping.join().to_str();
1228
1229                    let nv = self.uuid_to_spn(*group_ref)?;
1230                    let resolved_id = match nv {
1231                        Some(v) => v.to_proto_string_clone(),
1232                        None => uuid_to_proto_string(*group_ref),
1233                    };
1234
1235                    let joined = str_concat!(claims, ",");
1236
1237                    v.push(format!("{claim_name}:{resolved_id}:{join_char}:{joined:?}"))
1238                }
1239            }
1240            Ok(v)
1241        } else {
1242            let v: Vec<_> = value.to_proto_string_clone_iter().collect();
1243            Ok(v)
1244        }
1245    }
1246
1247    fn resolve_valueset_ldap(
1248        &mut self,
1249        value: &ValueSet,
1250        basedn: &str,
1251    ) -> Result<Vec<Vec<u8>>, OperationError> {
1252        if let Some(r_set) = value.as_refer_set() {
1253            let v: Result<Vec<_>, _> = r_set
1254                .iter()
1255                .copied()
1256                .map(|ur| {
1257                    let rdn = self.uuid_to_rdn(ur)?;
1258                    Ok(format!("{rdn},{basedn}").into_bytes())
1259                })
1260                .collect();
1261            v
1262        // We have to special case ssh keys here as the proto form isn't valid for
1263        // sss_ssh_authorized_keys to consume.
1264        } else if let Some(key_iter) = value.as_sshpubkey_string_iter() {
1265            let v: Vec<_> = key_iter.map(|s| s.into_bytes()).collect();
1266            Ok(v)
1267        } else {
1268            let v: Vec<_> = value
1269                .to_proto_string_clone_iter()
1270                .map(|s| s.into_bytes())
1271                .collect();
1272            Ok(v)
1273        }
1274    }
1275
1276    fn get_db_domain(&mut self) -> Result<Arc<EntrySealedCommitted>, OperationError> {
1277        self.internal_search_uuid(UUID_DOMAIN_INFO)
1278    }
1279
1280    fn get_domain_key_object_handle(&self) -> Result<Arc<KeyObject>, OperationError> {
1281        self.get_key_providers()
1282            .get_key_object_handle(UUID_DOMAIN_INFO)
1283            .ok_or(OperationError::KP0031KeyObjectNotFound)
1284    }
1285
1286    fn get_domain_es256_private_key(&mut self) -> Result<Vec<u8>, OperationError> {
1287        self.internal_search_uuid(UUID_DOMAIN_INFO)
1288            .and_then(|e| {
1289                e.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
1290                    .map(|s| s.to_vec())
1291                    .ok_or(OperationError::InvalidEntryState)
1292            })
1293            .map_err(|e| {
1294                admin_error!(?e, "Error getting domain es256 key");
1295                e
1296            })
1297    }
1298
1299    fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
1300        self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| {
1301            entry
1302                .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
1303                .unwrap_or(true)
1304        })
1305    }
1306
1307    /// Get the password badlist from the system config. You should not call this directly
1308    /// as this value is cached in the system_config() value.
1309    fn get_sc_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
1310        self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1311            .map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) {
1312                Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1313                None => HashSet::default(),
1314            })
1315            .map_err(|e| {
1316                error!(
1317                    ?e,
1318                    "Failed to retrieve password badlist from system configuration"
1319                );
1320                e
1321            })
1322    }
1323
1324    /// Get the denied name set from the system config. You should not call this directly
1325    /// as this value is cached in the system_config() value.
1326    fn get_sc_denied_names(&mut self) -> Result<HashSet<String>, OperationError> {
1327        self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1328            .map(|e| match e.get_ava_iter_iname(Attribute::DeniedName) {
1329                Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1330                None => HashSet::default(),
1331            })
1332            .map_err(|e| {
1333                error!(
1334                    ?e,
1335                    "Failed to retrieve denied names from system configuration"
1336                );
1337                e
1338            })
1339    }
1340
1341    fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1342        self.internal_search(filter!(f_eq(
1343            Attribute::Class,
1344            EntryClass::OAuth2ResourceServer.into(),
1345        )))
1346    }
1347
1348    fn get_applications_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1349        self.internal_search(filter!(f_eq(
1350            Attribute::Class,
1351            EntryClass::Application.into(),
1352        )))
1353    }
1354
1355    #[instrument(level = "debug", skip_all)]
1356    fn consumer_get_state(&mut self) -> Result<ReplRuvRange, OperationError> {
1357        // Get the current state of "where we are up to"
1358        //
1359        // There are two approaches we can use here. We can either store a cookie
1360        // related to the supplier we are fetching from, or we can use our RUV state.
1361        //
1362        // Initially I'm using RUV state, because it lets us select exactly what has
1363        // changed, where the cookie approach is more coarse grained. The cookie also
1364        // requires some more knowledge about what supplier we are communicating too
1365        // where the RUV approach doesn't since the supplier calcs the diff.
1366        //
1367        // We need the RUV as a state of
1368        //
1369        // [ s_uuid, cid_min, cid_max ]
1370        // [ s_uuid, cid_min, cid_max ]
1371        // [ s_uuid, cid_min, cid_max ]
1372        // ...
1373        //
1374        // This way the remote can diff against it's knowledge and work out:
1375        //
1376        // [ s_uuid, from_cid, to_cid ]
1377        // [ s_uuid, from_cid, to_cid ]
1378        //
1379        // ...
1380
1381        let domain_uuid = self.get_domain_uuid();
1382
1383        // Which then the supplier will use to actually retrieve the set of entries.
1384        // and the needed attributes we need.
1385        let ruv_snapshot = self.get_be_txn().get_ruv();
1386
1387        // What's the current set of ranges?
1388        ruv_snapshot
1389            .current_ruv_range()
1390            .map(|ranges| ReplRuvRange::V1 {
1391                domain_uuid,
1392                ranges,
1393            })
1394    }
1395}
1396
1397// Actually conduct a search request
1398// This is the core of the server, as it processes the entire event
1399// applies all parts required in order and more.
1400impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
1401    type AccessControlsTransactionType = AccessControlsReadTransaction<'a>;
1402    type BackendTransactionType = BackendReadTransaction<'a>;
1403    type SchemaTransactionType = SchemaReadTransaction;
1404    type KeyProvidersTransactionType = KeyProvidersReadTransaction;
1405
1406    fn get_be_txn(&mut self) -> &mut BackendReadTransaction<'a> {
1407        &mut self.be_txn
1408    }
1409
1410    fn get_schema<'b>(&self) -> &'b SchemaReadTransaction {
1411        // Strip the lifetime here. Schema is a sub-component of the transaction and is
1412        // *never* changed excepting in a write TXN, so we want to allow the schema to
1413        // be borrowed while the rest of the read txn is under a mut.
1414        unsafe {
1415            let s = (&self.schema) as *const _;
1416            &*s
1417        }
1418    }
1419
1420    fn get_accesscontrols(&self) -> &AccessControlsReadTransaction<'a> {
1421        &self.accesscontrols
1422    }
1423
1424    fn get_key_providers(&self) -> &KeyProvidersReadTransaction {
1425        &self.key_providers
1426    }
1427
1428    fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1429        Some(&mut self.resolve_filter_cache)
1430    }
1431
1432    fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig {
1433        &self.feature_config.hmac_name_history
1434    }
1435
1436    fn txn_name_to_uuid(&mut self) -> &mut BTreeMap<String, Uuid> {
1437        &mut self.txn_name_to_uuid
1438    }
1439
1440    fn get_resolve_filter_cache_and_be_txn(
1441        &mut self,
1442    ) -> (
1443        &mut BackendReadTransaction<'a>,
1444        Option<&mut ResolveFilterCacheReadTxn<'a>>,
1445    ) {
1446        (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1447    }
1448
1449    fn pw_badlist(&self) -> &HashSet<String> {
1450        &self.system_config.pw_badlist
1451    }
1452
1453    fn denied_names(&self) -> &HashSet<String> {
1454        &self.system_config.denied_names
1455    }
1456
1457    fn domain_info(&self) -> &DomainInfo {
1458        &self.d_info
1459    }
1460
1461    fn get_domain_version(&self) -> DomainVersion {
1462        self.d_info.d_vers
1463    }
1464
1465    fn get_domain_patch_level(&self) -> u32 {
1466        self.d_info.d_patch_level
1467    }
1468
1469    fn get_domain_development_taint(&self) -> bool {
1470        self.d_info.d_devel_taint
1471    }
1472
1473    fn get_domain_uuid(&self) -> Uuid {
1474        self.d_info.d_uuid
1475    }
1476
1477    fn get_domain_name(&self) -> &str {
1478        &self.d_info.d_name
1479    }
1480
1481    fn get_domain_display_name(&self) -> &str {
1482        &self.d_info.d_display
1483    }
1484
1485    fn get_domain_image_value(&self) -> Option<ImageValue> {
1486        self.d_info.d_image.clone()
1487    }
1488}
1489
1490impl QueryServerReadTransaction<'_> {
1491    pub(crate) fn trim_cid(&self) -> &Cid {
1492        &self.trim_cid
1493    }
1494
1495    /// Retrieve the domain info of this server
1496    pub fn public_domain_info(&mut self) -> Result<ProtoDomainInfo, OperationError> {
1497        let d_info = &self.d_info;
1498
1499        Ok(ProtoDomainInfo {
1500            name: d_info.d_name.clone(),
1501            displayname: d_info.d_display.clone(),
1502            uuid: d_info.d_uuid,
1503            level: d_info.d_vers,
1504        })
1505    }
1506
1507    /// Verify the data content of the server is as expected. This will probably
1508    /// call various functions for validation, including possibly plugin
1509    /// verifications.
1510    pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
1511        // If we fail after backend, we need to return NOW because we can't
1512        // assert any other faith in the DB states.
1513        //  * backend
1514        let be_errs = self.get_be_txn().verify();
1515
1516        if !be_errs.is_empty() {
1517            return be_errs;
1518        }
1519
1520        //  * in memory schema consistency.
1521        let sc_errs = self.get_schema().validate();
1522
1523        if !sc_errs.is_empty() {
1524            return sc_errs;
1525        }
1526
1527        // The schema is now valid, so we load this up
1528
1529        //  * Indexing (req be + sch )
1530        let idx_errs = self.get_be_txn().verify_indexes();
1531
1532        if !idx_errs.is_empty() {
1533            return idx_errs;
1534        }
1535
1536        // If anything error to this point we can't trust the verifications below. From
1537        // here we can just amass results.
1538        let mut results = Vec::with_capacity(0);
1539
1540        // Verify all our entries. Weird flex I know, but it's needed for verifying
1541        // the entry changelogs are consistent to their entries.
1542        let schema = self.get_schema();
1543
1544        let filt_all = filter!(f_pres(Attribute::Class));
1545        let all_entries = match self.internal_search(filt_all) {
1546            Ok(a) => a,
1547            Err(_e) => return vec![Err(ConsistencyError::QueryServerSearchFailure)],
1548        };
1549
1550        for e in all_entries {
1551            e.verify(schema, &mut results)
1552        }
1553
1554        // Verify the RUV to the entry changelogs now.
1555        self.get_be_txn().verify_ruv(&mut results);
1556
1557        // Ok entries passed, lets move on to the content.
1558        // Most of our checks are in the plugins, so we let them
1559        // do their job.
1560
1561        // Now, call the plugins verification system.
1562        Plugins::run_verify(self, &mut results);
1563        // Finished
1564
1565        results
1566    }
1567
1568    #[instrument(level = "debug", skip_all)]
1569    pub fn scim_entry_id_get_ext(
1570        &mut self,
1571        uuid: Uuid,
1572        class: EntryClass,
1573        query: ScimEntryGetQuery,
1574        ident: Identity,
1575    ) -> Result<ScimEntryKanidm, OperationError> {
1576        let filter_intent = filter!(f_and!([
1577            f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)),
1578            f_eq(Attribute::Class, class.into())
1579        ]));
1580
1581        let f_intent_valid = filter_intent
1582            .validate(self.get_schema())
1583            .map_err(OperationError::SchemaViolation)?;
1584
1585        let f_valid = f_intent_valid.clone().into_ignore_hidden();
1586
1587        let r_attrs = query
1588            .attributes
1589            .map(|attr_set| attr_set.into_iter().collect());
1590
1591        let se = SearchEvent {
1592            ident,
1593            filter: f_valid,
1594            filter_orig: f_intent_valid,
1595            attrs: r_attrs,
1596            effective_access_check: query.ext_access_check,
1597        };
1598
1599        let mut vs = self.search_ext(&se)?;
1600        match vs.pop() {
1601            Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
1602            _ => {
1603                if vs.is_empty() {
1604                    Err(OperationError::NoMatchingEntries)
1605                } else {
1606                    // Multiple entries matched, should not be possible!
1607                    Err(OperationError::UniqueConstraintViolation)
1608                }
1609            }
1610        }
1611    }
1612
1613    #[instrument(level = "debug", skip_all)]
1614    pub fn scim_search_ext(
1615        &mut self,
1616        ident: Identity,
1617        filter: ScimFilter,
1618        query: ScimEntryGetQuery,
1619    ) -> Result<ScimListResponse, OperationError> {
1620        let filter = if let Some(ref user_filter) = query.filter {
1621            ScimFilter::And(Box::new(filter), Box::new(user_filter.clone()))
1622        } else {
1623            filter
1624        };
1625
1626        let filter_intent = Filter::from_scim_ro(&ident, &filter, self)?;
1627
1628        self.scim_search_filter_ext(ident, &filter_intent, query)
1629    }
1630
1631    pub fn scim_search_filter_ext(
1632        &mut self,
1633        ident: Identity,
1634        filter_intent: &Filter<FilterInvalid>,
1635        query: ScimEntryGetQuery,
1636    ) -> Result<ScimListResponse, OperationError> {
1637        let f_intent_valid = filter_intent
1638            .validate(self.get_schema())
1639            .map_err(OperationError::SchemaViolation)?;
1640
1641        let f_valid = f_intent_valid.clone().into_ignore_hidden();
1642
1643        let r_attrs = query
1644            .attributes
1645            .map(|attr_set| attr_set.into_iter().collect());
1646
1647        let se = SearchEvent {
1648            ident,
1649            filter: f_valid,
1650            filter_orig: f_intent_valid,
1651            attrs: r_attrs,
1652            effective_access_check: query.ext_access_check,
1653        };
1654
1655        let mut result_set = self.search_ext(&se)?;
1656
1657        // We need to know total_results before we paginate.
1658        let total_results = result_set.len() as u64;
1659
1660        // These are STUPID ways to do this, but they demonstrate that the feature
1661        // works and it's viable on small datasets. We will make this use indexes
1662        // in the future!
1663
1664        // First, sort if any.
1665        if let Some(sort_attr) = query.sort_by {
1666            result_set.sort_unstable_by(|entry_left, entry_right| {
1667                let left = entry_left.get_ava_set(&sort_attr);
1668                let right = entry_right.get_ava_set(&sort_attr);
1669                match (left, right) {
1670                    (Some(left), Some(right)) => left.cmp(right),
1671                    (Some(_), None) => std::cmp::Ordering::Less,
1672                    (None, Some(_)) => std::cmp::Ordering::Greater,
1673                    (None, None) => std::cmp::Ordering::Equal,
1674                }
1675            });
1676        }
1677
1678        // Paginate, if any.
1679        let (items_per_page, start_index, paginated_result_set) = if let Some(count) = query.count {
1680            let count: u64 = count.get();
1681            // User wants pagination. Count is how many elements they want.
1682
1683            let start_index: u64 = query
1684                .start_index
1685                .map(|non_zero_index|
1686                    // SCIM pagination is 1 indexed, not 0.
1687                    non_zero_index.get() - 1)
1688                .unwrap_or_default();
1689
1690            // First, check that our start_index is valid.
1691            if start_index as usize > result_set.len() {
1692                // SCIM rfc doesn't define what happens if start index
1693                // is OOB of the result set.
1694                return Err(OperationError::SC0029PaginationOutOfBounds);
1695            }
1696
1697            let mut result_set = result_set.split_off(start_index as usize);
1698            result_set.truncate(count as usize);
1699
1700            (
1701                NonZeroU64::new(count),
1702                NonZeroU64::new(start_index + 1),
1703                result_set,
1704            )
1705        } else {
1706            // Unchanged
1707            (None, None, result_set)
1708        };
1709
1710        let resources = paginated_result_set
1711            .into_iter()
1712            .map(|entry| entry.to_scim_kanidm(self))
1713            .collect::<Result<Vec<_>, _>>()?;
1714
1715        Ok(ScimListResponse {
1716            // Requires other schema changes in future.
1717            schemas: Vec::with_capacity(0),
1718            total_results,
1719            items_per_page,
1720            start_index,
1721            resources,
1722        })
1723    }
1724
1725    #[instrument(level = "debug", skip_all)]
1726    pub fn scim_search_message_ready_ext(
1727        &mut self,
1728        ident: Identity,
1729        curtime: Duration,
1730    ) -> Result<ScimListResponse, OperationError> {
1731        let curtime_odt = OffsetDateTime::UNIX_EPOCH + curtime;
1732
1733        let filter_intent = filter_all!(f_and(vec![
1734            f_eq(Attribute::Class, EntryClass::OutboundMessage.into()),
1735            f_lt(Attribute::SendAfter, PartialValue::DateTime(curtime_odt)),
1736            f_andnot(f_pres(Attribute::SentAt))
1737        ]));
1738
1739        let query = ScimEntryGetQuery::default();
1740
1741        self.scim_search_filter_ext(ident, &filter_intent, query)
1742    }
1743}
1744
1745impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
1746    type AccessControlsTransactionType = AccessControlsWriteTransaction<'a>;
1747    type BackendTransactionType = BackendWriteTransaction<'a>;
1748    type SchemaTransactionType = SchemaWriteTransaction<'a>;
1749    type KeyProvidersTransactionType = KeyProvidersWriteTransaction<'a>;
1750
1751    fn get_be_txn(&mut self) -> &mut BackendWriteTransaction<'a> {
1752        &mut self.be_txn
1753    }
1754
1755    fn get_schema<'b>(&self) -> &'b SchemaWriteTransaction<'a> {
1756        // Strip the lifetime here. Schema is a sub-component of the transaction and is
1757        // *never* changed excepting in a write TXN, so we want to allow the schema to
1758        // be borrowed while the rest of the read txn is under a mut.
1759        unsafe {
1760            let s = (&self.schema) as *const _;
1761            &*s
1762        }
1763    }
1764
1765    fn get_accesscontrols(&self) -> &AccessControlsWriteTransaction<'a> {
1766        &self.accesscontrols
1767    }
1768
1769    fn get_key_providers(&self) -> &KeyProvidersWriteTransaction<'a> {
1770        &self.key_providers
1771    }
1772
1773    fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1774        if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1775            None
1776        } else {
1777            Some(&mut self.resolve_filter_cache)
1778        }
1779    }
1780
1781    fn get_feature_hmac_name_history_config(&self) -> &HmacNameHistoryConfig {
1782        &self.feature_config.hmac_name_history
1783    }
1784
1785    fn txn_name_to_uuid(&mut self) -> &mut BTreeMap<String, Uuid> {
1786        &mut self.txn_name_to_uuid
1787    }
1788
1789    fn get_resolve_filter_cache_and_be_txn(
1790        &mut self,
1791    ) -> (
1792        &mut BackendWriteTransaction<'a>,
1793        Option<&mut ResolveFilterCacheReadTxn<'a>>,
1794    ) {
1795        if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1796            (&mut self.be_txn, None)
1797        } else {
1798            (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1799        }
1800    }
1801
1802    fn pw_badlist(&self) -> &HashSet<String> {
1803        &self.system_config.pw_badlist
1804    }
1805
1806    fn denied_names(&self) -> &HashSet<String> {
1807        &self.system_config.denied_names
1808    }
1809
1810    fn domain_info(&self) -> &DomainInfo {
1811        &self.d_info
1812    }
1813
1814    fn get_domain_version(&self) -> DomainVersion {
1815        self.d_info.d_vers
1816    }
1817
1818    fn get_domain_patch_level(&self) -> u32 {
1819        self.d_info.d_patch_level
1820    }
1821
1822    fn get_domain_development_taint(&self) -> bool {
1823        self.d_info.d_devel_taint
1824    }
1825
1826    fn get_domain_uuid(&self) -> Uuid {
1827        self.d_info.d_uuid
1828    }
1829
1830    /// Gets the in-memory domain_name element
1831    fn get_domain_name(&self) -> &str {
1832        &self.d_info.d_name
1833    }
1834
1835    fn get_domain_display_name(&self) -> &str {
1836        &self.d_info.d_display
1837    }
1838
1839    fn get_domain_image_value(&self) -> Option<ImageValue> {
1840        self.d_info.d_image.clone()
1841    }
1842}
1843
1844impl QueryServer {
1845    pub fn new(
1846        be: Backend,
1847        schema: Schema,
1848        domain_name: String,
1849        curtime: Duration,
1850    ) -> Result<Self, OperationError> {
1851        let (s_uuid, d_uuid, ts_max) = {
1852            let mut wr = be.write()?;
1853            let s_uuid = wr.get_db_s_uuid()?;
1854            let d_uuid = wr.get_db_d_uuid()?;
1855            let ts_max = wr.get_db_ts_max(curtime)?;
1856            wr.commit()?;
1857            (s_uuid, d_uuid, ts_max)
1858        };
1859
1860        let pool_size = be.get_pool_size();
1861
1862        debug!("Server UUID -> {:?}", s_uuid);
1863        debug!("Domain UUID -> {:?}", d_uuid);
1864        debug!("Domain Name -> {:?}", domain_name);
1865
1866        let d_info = Arc::new(CowCell::new(DomainInfo {
1867            d_uuid,
1868            // Start with our level as zero.
1869            // This will be reloaded from the DB shortly :)
1870            d_vers: DOMAIN_LEVEL_0,
1871            d_patch_level: 0,
1872            d_name: domain_name.clone(),
1873            // we set the domain_display_name to the configuration file's domain_name
1874            // here because the database is not started, so we cannot pull it from there.
1875            d_display: domain_name,
1876            // Automatically derive our current taint mode based on the PRERELEASE setting.
1877            d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(),
1878            d_ldap_allow_unix_pw_bind: false,
1879            d_allow_easter_eggs: false,
1880            d_allow_account_recovery: false,
1881            d_image: None,
1882        }));
1883
1884        let cid = Cid::new_lamport(s_uuid, curtime, &ts_max);
1885        let cid_max = Arc::new(CowCell::new(cid));
1886
1887        // These default to empty, but they'll be populated shortly.
1888        let system_config = Arc::new(CowCell::new(SystemConfig::default()));
1889
1890        let feature_config = Arc::new(CowCell::new(FeatureConfig::default()));
1891
1892        let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
1893
1894        let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
1895
1896        let resolve_filter_cache = Arc::new(
1897            ARCacheBuilder::new()
1898                .set_size(RESOLVE_FILTER_CACHE_MAX, RESOLVE_FILTER_CACHE_LOCAL)
1899                .set_reader_quiesce(true)
1900                .build()
1901                .ok_or_else(|| {
1902                    error!("Failed to build filter resolve cache");
1903                    OperationError::DB0003FilterResolveCacheBuild
1904                })?,
1905        );
1906
1907        let key_providers = Arc::new(KeyProviders::default());
1908
1909        // These needs to be pool_size minus one to always leave a DB ticket
1910        // for a writer. But it also needs to be at least one :)
1911        debug_assert!(pool_size > 0);
1912        let read_ticket_pool = std::cmp::max(pool_size - 1, 1);
1913
1914        Ok(QueryServer {
1915            phase,
1916            d_info,
1917            system_config,
1918            feature_config,
1919            be,
1920            schema: Arc::new(schema),
1921            accesscontrols: Arc::new(AccessControls::default()),
1922            db_tickets: Arc::new(Semaphore::new(pool_size as usize)),
1923            read_tickets: Arc::new(Semaphore::new(read_ticket_pool as usize)),
1924            write_ticket: Arc::new(Semaphore::new(1)),
1925            resolve_filter_cache,
1926            dyngroup_cache,
1927            cid_max,
1928            key_providers,
1929        })
1930    }
1931
1932    pub fn try_quiesce(&self) {
1933        self.be.try_quiesce();
1934        self.accesscontrols.try_quiesce();
1935        self.resolve_filter_cache.try_quiesce();
1936    }
1937
1938    #[instrument(level = "debug", skip_all)]
1939    async fn read_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
1940        // Get a read ticket. Basically this forces us to queue with other readers, while preventing
1941        // us from competing with writers on the db tickets. This tilts us to write prioritising
1942        // on db operations by always making sure a writer can get a db ticket.
1943        let read_ticket = if cfg!(test) {
1944            self.read_tickets
1945                .try_acquire()
1946                .inspect_err(|err| {
1947                    error!(?err, "Unable to acquire read ticket!");
1948                })
1949                .ok()?
1950        } else {
1951            let fut = tokio::time::timeout(
1952                Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
1953                self.read_tickets.acquire(),
1954            );
1955
1956            match fut.await {
1957                Ok(Ok(ticket)) => ticket,
1958                Ok(Err(_)) => {
1959                    error!("Failed to acquire read ticket, may be poisoned.");
1960                    return None;
1961                }
1962                Err(_) => {
1963                    error!("Failed to acquire read ticket, server is overloaded.");
1964                    return None;
1965                }
1966            }
1967        };
1968
1969        // We need to ensure a db conn will be available. At this point either a db ticket
1970        // *must* be available because pool_size >= 2 and the only other holders are write
1971        // and read ticket holders, OR pool_size == 1, and we are waiting on the writer to now
1972        // complete.
1973        let db_ticket = if cfg!(test) {
1974            self.db_tickets
1975                .try_acquire()
1976                .inspect_err(|err| {
1977                    error!(?err, "Unable to acquire database ticket!");
1978                })
1979                .ok()?
1980        } else {
1981            self.db_tickets
1982                .acquire()
1983                .await
1984                .inspect_err(|err| {
1985                    error!(?err, "Unable to acquire database ticket!");
1986                })
1987                .ok()?
1988        };
1989
1990        Some((read_ticket, db_ticket))
1991    }
1992
1993    pub async fn read(&self) -> Result<QueryServerReadTransaction<'_>, OperationError> {
1994        let (read_ticket, db_ticket) = self
1995            .read_acquire_ticket()
1996            .await
1997            .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
1998        // Point of no return - we now have a DB thread AND the read ticket, we MUST complete
1999        // as soon as possible! The following locks and elements below are SYNCHRONOUS but
2000        // will never be contented at this point, and will always progress.
2001        let schema = self.schema.read();
2002
2003        let cid_max = self.cid_max.read();
2004        let trim_cid = cid_max.sub_secs(CHANGELOG_MAX_AGE)?;
2005
2006        let be_txn = self.be.read()?;
2007
2008        Ok(QueryServerReadTransaction {
2009            be_txn,
2010            schema,
2011            d_info: self.d_info.read(),
2012            system_config: self.system_config.read(),
2013            feature_config: self.feature_config.read(),
2014            accesscontrols: self.accesscontrols.read(),
2015            key_providers: self.key_providers.read(),
2016            _db_ticket: db_ticket,
2017            _read_ticket: read_ticket,
2018            resolve_filter_cache: self.resolve_filter_cache.read(),
2019            trim_cid,
2020            txn_name_to_uuid: Default::default(),
2021        })
2022    }
2023
2024    #[instrument(level = "debug", skip_all)]
2025    async fn write_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
2026        // Guarantee we are the only writer on the thread pool
2027        let write_ticket = if cfg!(test) {
2028            self.write_ticket
2029                .try_acquire()
2030                .inspect_err(|err| {
2031                    error!(?err, "Unable to acquire write ticket!");
2032                })
2033                .ok()?
2034        } else {
2035            let fut = tokio::time::timeout(
2036                Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
2037                self.write_ticket.acquire(),
2038            );
2039
2040            match fut.await {
2041                Ok(Ok(ticket)) => ticket,
2042                Ok(Err(_)) => {
2043                    error!("Failed to acquire write ticket, may be poisoned.");
2044                    return None;
2045                }
2046                Err(_) => {
2047                    error!("Failed to acquire write ticket, server is overloaded.");
2048                    return None;
2049                }
2050            }
2051        };
2052
2053        // We need to ensure a db conn will be available. At this point either a db ticket
2054        // *must* be available because pool_size >= 2 and the only other are readers, or
2055        // pool_size == 1 and we are waiting on a single reader to now complete
2056        let db_ticket = if cfg!(test) {
2057            self.db_tickets
2058                .try_acquire()
2059                .inspect_err(|err| {
2060                    error!(?err, "Unable to acquire write db_ticket!");
2061                })
2062                .ok()?
2063        } else {
2064            self.db_tickets
2065                .acquire()
2066                .await
2067                .inspect_err(|err| {
2068                    error!(?err, "Unable to acquire write db_ticket!");
2069                })
2070                .ok()?
2071        };
2072
2073        Some((write_ticket, db_ticket))
2074    }
2075
2076    pub async fn write(
2077        &self,
2078        curtime: Duration,
2079    ) -> Result<QueryServerWriteTransaction<'_>, OperationError> {
2080        let (write_ticket, db_ticket) = self
2081            .write_acquire_ticket()
2082            .await
2083            .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
2084
2085        // Point of no return - we now have a DB thread AND the write ticket, we MUST complete
2086        // as soon as possible! The following locks and elements below are SYNCHRONOUS but
2087        // will never be contented at this point, and will always progress.
2088
2089        let be_txn = self.be.write()?;
2090
2091        let schema_write = self.schema.write();
2092        let d_info = self.d_info.write();
2093        let system_config = self.system_config.write();
2094        let feature_config = self.feature_config.write();
2095        let phase = self.phase.write();
2096
2097        let mut cid = self.cid_max.write();
2098        // Update the cid now.
2099        *cid = Cid::new_lamport(cid.s_uuid, curtime, &cid.ts);
2100
2101        let trim_cid = cid.sub_secs(CHANGELOG_MAX_AGE)?;
2102
2103        Ok(QueryServerWriteTransaction {
2104            // I think this is *not* needed, because commit is mut self which should
2105            // take ownership of the value, and cause the commit to "only be run
2106            // once".
2107            //
2108            // The committed flag is however used for abort-specific code in drop
2109            // which today I don't think we have ... yet.
2110            committed: false,
2111            phase,
2112            d_info,
2113            system_config,
2114            feature_config,
2115            curtime,
2116            cid,
2117            trim_cid,
2118            be_txn,
2119            schema: schema_write,
2120            accesscontrols: self.accesscontrols.write(),
2121            changed_flags: ChangeFlag::empty(),
2122            changed_uuid: HashSet::new(),
2123            _db_ticket: db_ticket,
2124            _write_ticket: write_ticket,
2125            resolve_filter_cache: self.resolve_filter_cache.read(),
2126            resolve_filter_cache_clear: false,
2127            resolve_filter_cache_write: self.resolve_filter_cache.write(),
2128            dyngroup_cache: self.dyngroup_cache.write(),
2129            key_providers: self.key_providers.write(),
2130            txn_name_to_uuid: Default::default(),
2131        })
2132    }
2133
2134    #[cfg(any(test, debug_assertions))]
2135    pub async fn clear_cache(&self) -> Result<(), OperationError> {
2136        let ct = duration_from_epoch_now();
2137        let mut w_txn = self.write(ct).await?;
2138        w_txn.clear_cache()?;
2139        w_txn.commit()
2140    }
2141
2142    pub async fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
2143        let current_time = duration_from_epoch_now();
2144        // Before we can proceed, command the QS to load schema in full.
2145        // IMPORTANT: While we take a write txn, this does no writes to the
2146        // actual db, it's only so we can write to the in memory schema
2147        // structures.
2148        if self
2149            .write(current_time)
2150            .await
2151            .and_then(|mut txn| {
2152                txn.force_schema_reload();
2153                txn.commit()
2154            })
2155            .is_err()
2156        {
2157            return vec![Err(ConsistencyError::Unknown)];
2158        };
2159
2160        match self.read().await {
2161            Ok(mut r_txn) => r_txn.verify(),
2162            Err(_) => vec![Err(ConsistencyError::Unknown)],
2163        }
2164    }
2165}
2166
2167impl<'a> QueryServerWriteTransaction<'a> {
2168    pub(crate) fn get_server_uuid(&self) -> Uuid {
2169        // Cid has our server id within
2170        self.cid.s_uuid
2171    }
2172
2173    pub(crate) fn reset_server_uuid(&mut self) -> Result<(), OperationError> {
2174        let s_uuid = self.be_txn.reset_db_s_uuid().map_err(|err| {
2175            error!(?err, "Failed to reset server replication uuid");
2176            err
2177        })?;
2178
2179        debug!(?s_uuid, "reset server replication uuid");
2180
2181        self.cid.s_uuid = s_uuid;
2182
2183        Ok(())
2184    }
2185
2186    pub(crate) fn get_curtime(&self) -> Duration {
2187        self.curtime
2188    }
2189
2190    pub(crate) fn get_curtime_odt(&self) -> OffsetDateTime {
2191        OffsetDateTime::UNIX_EPOCH + self.curtime
2192    }
2193
2194    pub(crate) fn get_cid(&self) -> &Cid {
2195        &self.cid
2196    }
2197
2198    pub(crate) fn get_key_providers_mut(&mut self) -> &mut KeyProvidersWriteTransaction<'a> {
2199        &mut self.key_providers
2200    }
2201
2202    pub(crate) fn get_dyngroup_cache(&mut self) -> &mut DynGroupCache {
2203        &mut self.dyngroup_cache
2204    }
2205
2206    pub fn domain_raise(&mut self, level: u32) -> Result<(), OperationError> {
2207        if level > DOMAIN_MAX_LEVEL {
2208            return Err(OperationError::MG0002RaiseDomainLevelExceedsMaximum);
2209        }
2210
2211        let modl = ModifyList::new_purge_and_set(Attribute::Version, Value::Uint32(level));
2212        let udi = PVUUID_DOMAIN_INFO.clone();
2213        let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2214        self.internal_modify(&filt, &modl)
2215    }
2216
2217    pub fn domain_remigrate(&mut self, level: u32) -> Result<(), OperationError> {
2218        let mut_d_info = self.d_info.get_mut();
2219
2220        // NOTE: See reload_domain_info_version which asserts that we are not attempting
2221        // an unsupported remigration. This check is just a smoke check to no-op if we
2222        // are not requesting any meaningful action.
2223        if level > mut_d_info.d_vers {
2224            // Nothing to do.
2225            return Ok(());
2226        };
2227
2228        info!(
2229            "Preparing to re-migrate from {} -> {}",
2230            level, mut_d_info.d_vers
2231        );
2232        mut_d_info.d_vers = level;
2233        self.changed_flags.insert(ChangeFlag::DOMAIN);
2234
2235        Ok(())
2236    }
2237
2238    #[instrument(level = "debug", skip_all)]
2239    pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> {
2240        // supply entries to the writable schema to reload from.
2241        // find all attributes.
2242        let filt = filter!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2243        let res = self.internal_search(filt).map_err(|e| {
2244            admin_error!("reload schema internal search failed {:?}", e);
2245            e
2246        })?;
2247        // load them.
2248        let attributetypes: Result<Vec<_>, _> =
2249            res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
2250
2251        let attributetypes = attributetypes.map_err(|e| {
2252            admin_error!("reload schema attributetypes {:?}", e);
2253            e
2254        })?;
2255
2256        self.schema.update_attributes(attributetypes).map_err(|e| {
2257            admin_error!("reload schema update attributetypes {:?}", e);
2258            e
2259        })?;
2260
2261        // find all classes
2262        let filt = filter!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
2263        let res = self.internal_search(filt).map_err(|e| {
2264            admin_error!("reload schema internal search failed {:?}", e);
2265            e
2266        })?;
2267        // load them.
2268        let classtypes: Result<Vec<_>, _> = res.iter().map(|e| SchemaClass::try_from(e)).collect();
2269        let classtypes = classtypes.map_err(|e| {
2270            admin_error!("reload schema classtypes {:?}", e);
2271            e
2272        })?;
2273
2274        self.schema.update_classes(classtypes).map_err(|e| {
2275            admin_error!("reload schema update classtypes {:?}", e);
2276            e
2277        })?;
2278
2279        // validate.
2280        let valid_r = self.schema.validate();
2281
2282        // Translate the result.
2283        if valid_r.is_empty() {
2284            // Now use this to reload the backend idxmeta
2285            trace!("Reloading idxmeta ...");
2286            self.be_txn
2287                .update_idxmeta(self.schema.reload_idxmeta())
2288                .map_err(|e| {
2289                    admin_error!("reload schema update idxmeta {:?}", e);
2290                    e
2291                })
2292        } else {
2293            // Log the failures?
2294            admin_error!("Schema reload failed -> {:?}", valid_r);
2295            Err(OperationError::ConsistencyError(
2296                valid_r.into_iter().filter_map(|v| v.err()).collect(),
2297            ))
2298        }?;
2299
2300        // Since we reloaded the schema, we need to reload the filter cache since it
2301        // may have incorrect or outdated information about indexes now.
2302        self.resolve_filter_cache_clear = true;
2303
2304        // Trigger reloads on services that require post-schema reloads.
2305        // Mainly this is plugins.
2306        DynGroup::reload(self)?;
2307
2308        Ok(())
2309    }
2310
2311    #[instrument(level = "debug", skip_all)]
2312    fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
2313        // supply entries to the writable access controls to reload from.
2314        // This has to be done in FOUR passes - one for each type!
2315        //
2316        // Note, we have to do the search, parse, then submit here, because of the
2317        // requirement to have the write query server reference in the parse stage - this
2318        // would cause a rust double-borrow if we had AccessControls to try to handle
2319        // the entry lists themself.
2320        trace!("ACP reload started ...");
2321
2322        // Update the set of sync agreements
2323
2324        let filt = filter!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
2325
2326        let res = self.internal_search(filt).map_err(|e| {
2327            admin_error!(
2328                err = ?e,
2329                "reload accesscontrols internal search failed",
2330            );
2331            e
2332        })?;
2333
2334        let sync_agreement_map: HashMap<Uuid, BTreeSet<Attribute>> = res
2335            .iter()
2336            .filter_map(|e| {
2337                e.get_ava_as_iutf8(Attribute::SyncYieldAuthority)
2338                    .map(|set| {
2339                        let set: BTreeSet<_> =
2340                            set.iter().map(|s| Attribute::from(s.as_str())).collect();
2341                        (e.get_uuid(), set)
2342                    })
2343            })
2344            .collect();
2345
2346        self.accesscontrols
2347            .update_sync_agreements(sync_agreement_map);
2348
2349        // Update search
2350        let filt = filter!(f_and!([
2351            f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2352            f_eq(Attribute::Class, EntryClass::AccessControlSearch.into()),
2353            f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2354        ]));
2355
2356        let res = self.internal_search(filt).map_err(|e| {
2357            admin_error!(
2358                err = ?e,
2359                "reload accesscontrols internal search failed",
2360            );
2361            e
2362        })?;
2363        let search_acps: Result<Vec<_>, _> = res
2364            .iter()
2365            .map(|e| AccessControlSearch::try_from(self, e))
2366            .collect();
2367
2368        let search_acps = search_acps.map_err(|e| {
2369            admin_error!(err = ?e, "Unable to parse search accesscontrols");
2370            e
2371        })?;
2372
2373        self.accesscontrols
2374            .update_search(search_acps)
2375            .map_err(|e| {
2376                admin_error!(err = ?e, "Failed to update search accesscontrols");
2377                e
2378            })?;
2379        // Update create
2380        let filt = filter!(f_and!([
2381            f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2382            f_eq(Attribute::Class, EntryClass::AccessControlCreate.into()),
2383            f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2384        ]));
2385
2386        let res = self.internal_search(filt).map_err(|e| {
2387            admin_error!(
2388                err = ?e,
2389                "reload accesscontrols internal search failed"
2390            );
2391            e
2392        })?;
2393        let create_acps: Result<Vec<_>, _> = res
2394            .iter()
2395            .map(|e| AccessControlCreate::try_from(self, e))
2396            .collect();
2397
2398        let create_acps = create_acps.map_err(|e| {
2399            admin_error!(err = ?e, "Unable to parse create accesscontrols");
2400            e
2401        })?;
2402
2403        self.accesscontrols
2404            .update_create(create_acps)
2405            .map_err(|e| {
2406                admin_error!(err = ?e, "Failed to update create accesscontrols");
2407                e
2408            })?;
2409        // Update modify
2410        let filt = filter!(f_and!([
2411            f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2412            f_eq(Attribute::Class, EntryClass::AccessControlModify.into()),
2413            f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2414        ]));
2415
2416        let res = self.internal_search(filt).map_err(|e| {
2417            admin_error!("reload accesscontrols internal search failed {:?}", e);
2418            e
2419        })?;
2420        let modify_acps: Result<Vec<_>, _> = res
2421            .iter()
2422            .map(|e| AccessControlModify::try_from(self, e))
2423            .collect();
2424
2425        let modify_acps = modify_acps.map_err(|e| {
2426            admin_error!("Unable to parse modify accesscontrols {:?}", e);
2427            e
2428        })?;
2429
2430        self.accesscontrols
2431            .update_modify(modify_acps)
2432            .map_err(|e| {
2433                admin_error!("Failed to update modify accesscontrols {:?}", e);
2434                e
2435            })?;
2436        // Update delete
2437        let filt = filter!(f_and!([
2438            f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2439            f_eq(Attribute::Class, EntryClass::AccessControlDelete.into()),
2440            f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2441        ]));
2442
2443        let res = self.internal_search(filt).map_err(|e| {
2444            admin_error!("reload accesscontrols internal search failed {:?}", e);
2445            e
2446        })?;
2447        let delete_acps: Result<Vec<_>, _> = res
2448            .iter()
2449            .map(|e| AccessControlDelete::try_from(self, e))
2450            .collect();
2451
2452        let delete_acps = delete_acps.map_err(|e| {
2453            admin_error!("Unable to parse delete accesscontrols {:?}", e);
2454            e
2455        })?;
2456
2457        self.accesscontrols.update_delete(delete_acps).map_err(|e| {
2458            admin_error!("Failed to update delete accesscontrols {:?}", e);
2459            e
2460        })
2461    }
2462
2463    #[instrument(level = "debug", skip_all)]
2464    pub(crate) fn reload_key_material(&mut self) -> Result<(), OperationError> {
2465        let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
2466
2467        let res = self.internal_search(filt).map_err(|e| {
2468            admin_error!(
2469                err = ?e,
2470                "reload key providers internal search failed",
2471            );
2472            e
2473        })?;
2474
2475        // FUTURE: During this reload we may need to access the PIN or other data
2476        // to access the provider.
2477        let providers = res
2478            .iter()
2479            .map(|e| KeyProvider::try_from(e).and_then(|kp| kp.test().map(|()| kp)))
2480            .collect::<Result<Vec<_>, _>>()?;
2481
2482        self.key_providers.update_providers(providers)?;
2483
2484        let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
2485
2486        let res = self.internal_search(filt).map_err(|e| {
2487            admin_error!(
2488                err = ?e,
2489                "reload key objects internal search failed",
2490            );
2491            e
2492        })?;
2493
2494        res.iter()
2495            .try_for_each(|entry| self.key_providers.load_key_object(entry.as_ref()))
2496    }
2497
2498    #[instrument(level = "debug", skip_all)]
2499    pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
2500        let denied_names = self.get_sc_denied_names()?;
2501        let pw_badlist = self.get_sc_password_badlist()?;
2502
2503        let mut_system_config = self.system_config.get_mut();
2504        mut_system_config.denied_names = denied_names;
2505        mut_system_config.pw_badlist = pw_badlist;
2506        Ok(())
2507    }
2508
2509    /// Pulls the domain name from the database and updates the DomainInfo data in memory
2510    #[instrument(level = "debug", skip_all)]
2511    pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
2512        let domain_info = self.internal_search_uuid(UUID_DOMAIN_INFO).map_err(|err| {
2513            error!(?err, "Error getting domain info");
2514            err
2515        })?;
2516
2517        let domain_info_version = domain_info
2518            .get_ava_single_uint32(Attribute::Version)
2519            .ok_or_else(|| {
2520                error!("domain info missing attribute version");
2521                OperationError::InvalidEntryState
2522            })?;
2523
2524        let domain_info_patch_level = domain_info
2525            .get_ava_single_uint32(Attribute::PatchLevel)
2526            .unwrap_or(0);
2527
2528        // If we have moved from stable to dev, this triggers the taint. If we
2529        // are moving from dev to stable, the db will be true triggering the
2530        // taint flag. If we are stable to stable this will be false.
2531        let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
2532        let domain_info_devel_taint = current_devel_flag
2533            || domain_info
2534                .get_ava_single_bool(Attribute::DomainDevelopmentTaint)
2535                .unwrap_or_default();
2536
2537        let domain_allow_easter_eggs = domain_info
2538            .get_ava_single_bool(Attribute::DomainAllowEasterEggs)
2539            // This defaults to false for release versions, and true in development
2540            .unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some());
2541
2542        let domain_allow_account_recovery = domain_info
2543            .get_ava_single_bool(Attribute::DomainAllowAccountRecovery)
2544            .unwrap_or_default();
2545
2546        // We have to set the domain version here so that features which check for it
2547        // will now see it's been increased. This also prevents recursion during reloads
2548        // inside of a domain migration.
2549        let mut_d_info = self.d_info.get_mut();
2550        debug!(?mut_d_info);
2551        // This is the value that is set as part of re-migrate.
2552        let previous_version = mut_d_info.d_vers;
2553        let previous_patch_level = mut_d_info.d_patch_level;
2554        mut_d_info.d_vers = domain_info_version;
2555        mut_d_info.d_patch_level = domain_info_patch_level;
2556        mut_d_info.d_devel_taint = domain_info_devel_taint;
2557        mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs;
2558        mut_d_info.d_allow_account_recovery = domain_allow_account_recovery;
2559
2560        debug!(?mut_d_info);
2561
2562        // We must both be at the correct domain version *and* the correct patch level. If we are
2563        // not, then we only proceed to migrate *if* our server boot phase is correct.
2564        if (previous_version == domain_info_version
2565            && previous_patch_level == domain_info_patch_level)
2566            || *self.phase < ServerPhase::DomainInfoReady
2567        {
2568            return Ok(());
2569        }
2570
2571        debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2572        debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2573
2574        // We have to check for DL0 since that's the initialisation level. If we are at DL0 then
2575        // the server was just brought up and there are no other actions to take since we are
2576        // now at TGT level.
2577        if previous_version == DOMAIN_LEVEL_0 {
2578            debug!(
2579                "Server was just brought up, skipping migrations as we are already at target level"
2580            );
2581            return Ok(());
2582        }
2583
2584        if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL {
2585            let valid_levels: Vec<_> =
2586                (DOMAIN_MIN_REMIGRATION_LEVEL..DOMAIN_PREVIOUS_TGT_LEVEL).collect();
2587            error!("UNABLE TO PROCEED. You have requested an initial migration level which is lower than supported.");
2588            error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
2589            error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2590            error!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2591            error!(?valid_levels);
2592
2593            debug_assert!(false);
2594
2595            return Err(OperationError::MG0001InvalidReMigrationLevel);
2596        }
2597
2598        // Commented as an example of patch application
2599        /*
2600        if previous_patch_level < PATCH_LEVEL_2
2601            && domain_info_patch_level >= PATCH_LEVEL_2
2602            && domain_info_version == DOMAIN_LEVEL_9
2603        {
2604            self.migrate_domain_patch_level_2()?;
2605        }
2606        */
2607
2608        // This is to catch during development if we incorrectly move MIN_REMIGRATION but
2609        // without actually updating these values correctly.
2610        const { assert!(DOMAIN_MIN_REMIGRATION_LEVEL <= DOMAIN_PREVIOUS_TGT_LEVEL) };
2611        const { assert!(DOMAIN_MIN_REMIGRATION_LEVEL >= DOMAIN_MIN_CREATION_LEVEL) };
2612
2613        const { assert!(DOMAIN_MIN_CREATION_LEVEL >= DOMAIN_LEVEL_10) };
2614
2615        //                     /--- This needs to be the minimum creation level.
2616        //                     |                                          /-- This is the minlevel we can remigrate from
2617        //                     v                                          v
2618        if previous_version <= DOMAIN_LEVEL_10 && domain_info_version >= DOMAIN_LEVEL_11 {
2619            // 1.6 -> 1.7
2620            self.migrate_domain_10_to_11()?;
2621        }
2622
2623        if previous_version <= DOMAIN_LEVEL_11 && domain_info_version >= DOMAIN_LEVEL_12 {
2624            // 1.7 -> 1.8
2625            self.migrate_domain_11_to_12()?;
2626        }
2627
2628        if previous_version <= DOMAIN_LEVEL_12 && domain_info_version >= DOMAIN_LEVEL_13 {
2629            // 1.8 -> 1.9
2630            self.migrate_domain_12_to_13()?;
2631        }
2632
2633        if previous_version <= DOMAIN_LEVEL_13 && domain_info_version >= DOMAIN_LEVEL_14 {
2634            // 1.9 -> 1.10
2635            self.migrate_domain_13_to_14()?;
2636        }
2637
2638        if previous_version <= DOMAIN_LEVEL_14 && domain_info_version >= DOMAIN_LEVEL_15 {
2639            // 1.10 -> 1.11
2640            self.migrate_domain_14_to_15()?;
2641        }
2642
2643        // This is here to catch when we increase domain levels but didn't create the migration
2644        // hooks. If this fails it probably means you need to add another migration hook
2645        // in the above.
2646        const { assert!(DOMAIN_MAX_LEVEL == DOMAIN_LEVEL_15) };
2647        debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
2648
2649        Ok(())
2650    }
2651
2652    /// Pulls the domain name from the database and updates the DomainInfo data in memory
2653    #[instrument(level = "debug", skip_all)]
2654    pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
2655        let domain_entry = self.get_db_domain()?;
2656
2657        let domain_name = domain_entry
2658            .get_ava_single_iname(Attribute::DomainName)
2659            .map(str::to_string)
2660            .ok_or(OperationError::InvalidEntryState)?;
2661
2662        let display_name = domain_entry
2663            .get_ava_single_utf8(Attribute::DomainDisplayName)
2664            .map(str::to_string)
2665            .unwrap_or_else(|| format!("Kanidm {domain_name}"));
2666
2667        let domain_ldap_allow_unix_pw_bind = domain_entry
2668            .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
2669            .unwrap_or(true);
2670
2671        let domain_image = domain_entry.get_ava_single_image(Attribute::Image);
2672
2673        let domain_uuid = self.be_txn.get_db_d_uuid()?;
2674
2675        let mut_d_info = self.d_info.get_mut();
2676        mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind;
2677        if mut_d_info.d_uuid != domain_uuid {
2678            admin_warn!(
2679                "Using domain uuid from the database {} - was {} in memory",
2680                domain_name,
2681                mut_d_info.d_name,
2682            );
2683            mut_d_info.d_uuid = domain_uuid;
2684        }
2685        if mut_d_info.d_name != domain_name {
2686            admin_warn!(
2687                "Using domain name from the database {} - was {} in memory",
2688                domain_name,
2689                mut_d_info.d_name,
2690            );
2691            admin_warn!(
2692                    "If you think this is an error, see https://kanidm.github.io/kanidm/master/domain_rename.html"
2693                );
2694            mut_d_info.d_name = domain_name;
2695        }
2696        mut_d_info.d_display = display_name;
2697        mut_d_info.d_image = domain_image;
2698        Ok(())
2699    }
2700
2701    /// Reloads feature configurations if they have changed in this operation
2702    #[instrument(level = "debug", skip_all)]
2703    pub(crate) fn reload_feature_config(&mut self) -> Result<(), OperationError> {
2704        let filt = filter!(f_eq(Attribute::Class, EntryClass::Feature.into()));
2705
2706        let feature_configs = self.internal_search(filt).inspect_err(|err| {
2707            error!(?err, "reload feature configuration internal search failed",)
2708        })?;
2709
2710        let current_time = self.get_curtime();
2711        let domain_level = self.get_domain_version();
2712
2713        let mut hmac_name_history_fixup = false;
2714
2715        // TODO: How to handle disabling on a delete? Needs thought ... but also
2716        // should be impossible for someone TO delete a feature config entry?
2717
2718        for feature_entry in feature_configs {
2719            match feature_entry.get_uuid() {
2720                UUID_HMAC_NAME_FEATURE => {
2721                    if domain_level < DOMAIN_LEVEL_12 {
2722                        trace!("Skipping hmac name history config");
2723                        continue;
2724                    }
2725
2726                    let key_object = self
2727                        .get_key_providers()
2728                        .get_key_object_handle(UUID_HMAC_NAME_FEATURE)
2729                        .ok_or(OperationError::KP0079KeyObjectNotFound)?;
2730
2731                    let mut key = HmacSha256Key::default();
2732                    key_object.hkdf_s256_expand(
2733                        UUID_HMAC_NAME_FEATURE.as_bytes(),
2734                        key.as_mut_slice(),
2735                        current_time,
2736                    )?;
2737
2738                    drop(key_object);
2739
2740                    let new_feature_enabled_state = feature_entry
2741                        .get_ava_single_bool(Attribute::Enabled)
2742                        .unwrap_or_default();
2743
2744                    let feature_config_txn = self.feature_config.get_mut();
2745
2746                    hmac_name_history_fixup =
2747                        !feature_config_txn.hmac_name_history.enabled && new_feature_enabled_state;
2748
2749                    feature_config_txn.hmac_name_history.enabled = new_feature_enabled_state;
2750
2751                    std::mem::swap(&mut key, &mut feature_config_txn.hmac_name_history.key);
2752                }
2753                feature_uuid => {
2754                    error!(
2755                        ?feature_uuid,
2756                        "Unrecognised feature uuid, unable to proceed"
2757                    );
2758                    return Err(OperationError::KG004UnknownFeatureUuid);
2759                }
2760            }
2761        }
2762
2763        if hmac_name_history_fixup {
2764            plugins::hmac_name_unique::HmacNameUnique::fixup(self)?;
2765        }
2766
2767        Ok(())
2768    }
2769
2770    /// Initiate a domain display name change process. This isn't particularly scary
2771    /// because it's just a wibbly human-facing thing, not used for secure
2772    /// activities (yet)
2773    pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2774        let modl = ModifyList::new_purge_and_set(
2775            Attribute::DomainDisplayName,
2776            Value::new_utf8(new_domain_name.to_string()),
2777        );
2778        let udi = PVUUID_DOMAIN_INFO.clone();
2779        let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2780        self.internal_modify(&filt, &modl)
2781    }
2782
2783    /// Initiate a domain rename process. This is generally an internal function but it's
2784    /// exposed to the cli for admins to be able to initiate the process.
2785    ///
2786    /// # Safety
2787    /// This is UNSAFE because while it may change the domain name, it doesn't update
2788    /// the running configured version of the domain name that is resident to the
2789    /// query server.
2790    ///
2791    /// Currently it's only used to test what happens if we rename the domain and how
2792    /// that impacts spns, but in the future we may need to reconsider how this is
2793    /// approached, especially if we have a domain re-name replicated to us. It could
2794    /// be that we end up needing to have this as a cow cell or similar?
2795    pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2796        let modl =
2797            ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name));
2798        let udi = PVUUID_DOMAIN_INFO.clone();
2799        let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2800        self.internal_modify(&filt, &modl)
2801    }
2802
2803    pub fn reindex(&mut self, immediate: bool) -> Result<(), OperationError> {
2804        // initiate a be reindex here. This could have been from first run checking
2805        // the versions, or it could just be from the cli where an admin needs to do an
2806        // indexing.
2807        self.be_txn.reindex(immediate)
2808    }
2809
2810    fn force_schema_reload(&mut self) {
2811        self.changed_flags.insert(ChangeFlag::SCHEMA);
2812    }
2813
2814    fn force_domain_reload(&mut self) {
2815        self.changed_flags.insert(ChangeFlag::DOMAIN);
2816    }
2817
2818    pub(crate) fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> {
2819        self.be_txn.upgrade_reindex(v)
2820    }
2821
2822    #[inline]
2823    pub(crate) fn get_changed_app(&self) -> bool {
2824        self.changed_flags.contains(ChangeFlag::APPLICATION)
2825    }
2826
2827    #[inline]
2828    pub(crate) fn get_changed_oauth2(&self) -> bool {
2829        self.changed_flags.contains(ChangeFlag::OAUTH2)
2830    }
2831
2832    #[inline]
2833    pub(crate) fn clear_changed_oauth2(&mut self) {
2834        self.changed_flags.remove(ChangeFlag::OAUTH2)
2835    }
2836
2837    #[inline]
2838    pub(crate) fn get_changed_oauth2_client(&self) -> bool {
2839        self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
2840    }
2841
2842    /// Indicate that we are about to re-bootstrap this server. You should ONLY
2843    /// call this during a replication refresh!!!
2844    pub(crate) fn set_phase_bootstrap(&mut self) {
2845        *self.phase = ServerPhase::Bootstrap;
2846    }
2847
2848    /// Raise the currently running server phase.
2849    pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
2850        // Phase changes are one way
2851        if phase > *self.phase {
2852            *self.phase = phase
2853        }
2854    }
2855
2856    pub(crate) fn get_phase(&mut self) -> ServerPhase {
2857        *self.phase
2858    }
2859
2860    pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
2861        // First, check if the domain version has changed. This can trigger
2862        // changes to schema, access controls and more.
2863        if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2864            self.reload_domain_info_version()?;
2865        }
2866
2867        // This could be faster if we cache the set of classes changed
2868        // in an operation so we can check if we need to do the reload or not
2869        //
2870        // Reload the schema from qs.
2871        if self.changed_flags.intersects(ChangeFlag::SCHEMA) {
2872            self.reload_schema()?;
2873
2874            // If the server is in a late phase of start up or is
2875            // operational, then a reindex may be required. After the reindex, the schema
2876            // must also be reloaded so that slope optimisation indexes are loaded correctly.
2877            if *self.phase >= ServerPhase::Running {
2878                self.reindex(false)?;
2879                self.reload_schema()?;
2880            }
2881        }
2882
2883        // We need to reload cryptographic providers before anything else so that
2884        // sync agreements and the domain can access their key material.
2885        if self
2886            .changed_flags
2887            .intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
2888        {
2889            self.reload_key_material()?;
2890        }
2891
2892        // Determine if we need to update access control profiles
2893        // based on any modifications that have occurred.
2894        // IF SCHEMA CHANGED WE MUST ALSO RELOAD!!! IE if schema had an attr removed
2895        // that we rely on we MUST fail this here!!
2896        //
2897        // Also note that changing sync agreements triggers an acp reload since
2898        // access controls need to be aware of these agreements.
2899        if self
2900            .changed_flags
2901            .intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
2902        {
2903            self.reload_accesscontrols()?;
2904        } else {
2905            // On a reload the cache is dropped, otherwise we tell accesscontrols
2906            // to drop anything related that was changed.
2907            // self.accesscontrols
2908            //    .invalidate_related_cache(self.changed_uuid.into_inner().as_slice())
2909        }
2910
2911        if self.changed_flags.intersects(ChangeFlag::SYSTEM_CONFIG) {
2912            self.reload_system_config()?;
2913        }
2914
2915        if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2916            self.reload_domain_info()?;
2917        }
2918
2919        if self
2920            .changed_flags
2921            .intersects(ChangeFlag::FEATURE | ChangeFlag::KEY_MATERIAL)
2922        {
2923            self.reload_feature_config()?;
2924        }
2925
2926        // Clear flags
2927        self.changed_flags.remove(
2928            ChangeFlag::DOMAIN
2929                | ChangeFlag::SCHEMA
2930                | ChangeFlag::FEATURE
2931                | ChangeFlag::SYSTEM_CONFIG
2932                | ChangeFlag::ACP
2933                | ChangeFlag::SYNC_AGREEMENT
2934                | ChangeFlag::KEY_MATERIAL,
2935        );
2936
2937        Ok(())
2938    }
2939
2940    #[cfg(any(test, debug_assertions))]
2941    #[instrument(level = "debug", skip_all)]
2942    pub fn clear_cache(&mut self) -> Result<(), OperationError> {
2943        self.be_txn.clear_cache()
2944    }
2945
2946    #[instrument(level = "debug", name="qswt_commit" skip_all)]
2947    pub fn commit(mut self) -> Result<(), OperationError> {
2948        self.reload()?;
2949
2950        // Now destructure the transaction ready to reset it.
2951        let QueryServerWriteTransaction {
2952            committed,
2953            phase,
2954            d_info,
2955            system_config,
2956            feature_config,
2957            mut be_txn,
2958            schema,
2959            accesscontrols,
2960            cid,
2961            dyngroup_cache,
2962            key_providers,
2963            // Hold these for a bit more ...
2964            _db_ticket,
2965            _write_ticket,
2966            // Ignore values that don't need a commit.
2967            curtime: _,
2968            trim_cid: _,
2969            changed_flags,
2970            changed_uuid: _,
2971            resolve_filter_cache: _,
2972            resolve_filter_cache_clear,
2973            mut resolve_filter_cache_write,
2974            txn_name_to_uuid: _,
2975        } = self;
2976        debug_assert!(!committed);
2977
2978        // Should have been cleared by any reloads.
2979        trace!(
2980            changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
2981        );
2982
2983        // Write the cid to the db. If this fails, we can't assume replication
2984        // will be stable, so return if it fails.
2985        be_txn.set_db_ts_max(cid.ts)?;
2986        cid.commit();
2987
2988        // We don't care if this passes/fails, committing this is fine.
2989        if resolve_filter_cache_clear {
2990            resolve_filter_cache_write.clear();
2991        }
2992        resolve_filter_cache_write.commit();
2993
2994        // Point of no return - everything has been validated and reloaded.
2995        //
2996        // = Lets commit =
2997        schema
2998            .commit()
2999            .map(|_| d_info.commit())
3000            .map(|_| system_config.commit())
3001            .map(|_| feature_config.commit())
3002            .map(|_| phase.commit())
3003            .map(|_| dyngroup_cache.commit())
3004            .and_then(|_| key_providers.commit())
3005            .and_then(|_| accesscontrols.commit())
3006            .and_then(|_| be_txn.commit())
3007    }
3008
3009    pub(crate) fn get_txn_cid(&self) -> &Cid {
3010        &self.cid
3011    }
3012}
3013
3014#[cfg(test)]
3015mod tests {
3016    use crate::prelude::*;
3017    use kanidm_proto::scim_v1::{
3018        server::{ScimListResponse, ScimReference},
3019        JsonValue, ScimEntryGetQuery, ScimFilter,
3020    };
3021    use std::num::NonZeroU64;
3022
3023    #[qs_test]
3024    async fn test_name_to_uuid(server: &QueryServer) {
3025        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3026
3027        let t_uuid = Uuid::new_v4();
3028        assert!(server_txn
3029            .internal_create(vec![entry_init!(
3030                (Attribute::Class, EntryClass::Object.to_value()),
3031                (Attribute::Class, EntryClass::Account.to_value()),
3032                (Attribute::Class, EntryClass::Person.to_value()),
3033                (Attribute::Name, Value::new_iname("testperson1")),
3034                (Attribute::Uuid, Value::Uuid(t_uuid)),
3035                (Attribute::Description, Value::new_utf8s("testperson1")),
3036                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3037            ),])
3038            .is_ok());
3039
3040        // Name doesn't exist
3041        let r1 = server_txn.name_to_uuid("testpers");
3042        assert!(r1.is_err());
3043        // Name doesn't exist (not syntax normalised)
3044        let r2 = server_txn.name_to_uuid("tEsTpErS");
3045        assert!(r2.is_err());
3046        // Name does exist
3047        let r3 = server_txn.name_to_uuid("testperson1");
3048        assert_eq!(r3, Ok(t_uuid));
3049        // Name is not syntax normalised (but exists)
3050        let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
3051        assert_eq!(r4, Ok(t_uuid));
3052        // Name is an rdn
3053        let r5 = server_txn.name_to_uuid("name=testperson1");
3054        assert_eq!(r5, Ok(t_uuid));
3055        // Name is a dn
3056        let r6 = server_txn.name_to_uuid("name=testperson1,o=example");
3057        assert_eq!(r6, Ok(t_uuid));
3058    }
3059
3060    #[qs_test]
3061    async fn test_external_id_to_uuid(server: &QueryServer) {
3062        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3063
3064        let t_uuid = Uuid::new_v4();
3065        assert!(server_txn
3066            .internal_create(vec![entry_init!(
3067                (Attribute::Class, EntryClass::Object.to_value()),
3068                (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3069                (Attribute::Uuid, Value::Uuid(t_uuid)),
3070                (
3071                    Attribute::SyncExternalId,
3072                    Value::new_iutf8("uid=testperson")
3073                )
3074            ),])
3075            .is_ok());
3076
3077        // Name doesn't exist
3078        let r1 = server_txn.sync_external_id_to_uuid("tobias");
3079        assert_eq!(r1, Ok(None));
3080        // Name doesn't exist (not syntax normalised)
3081        let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
3082        assert_eq!(r2, Ok(None));
3083        // Name does exist
3084        let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
3085        assert_eq!(r3, Ok(Some(t_uuid)));
3086        // Name is not syntax normalised (but exists)
3087        let r4 = server_txn.sync_external_id_to_uuid("uId=TeStPeRsOn");
3088        assert_eq!(r4, Ok(Some(t_uuid)));
3089    }
3090
3091    #[qs_test]
3092    async fn test_uuid_to_spn(server: &QueryServer) {
3093        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3094
3095        let e1 = entry_init!(
3096            (Attribute::Class, EntryClass::Object.to_value()),
3097            (Attribute::Class, EntryClass::Person.to_value()),
3098            (Attribute::Class, EntryClass::Account.to_value()),
3099            (Attribute::Name, Value::new_iname("testperson1")),
3100            (
3101                Attribute::Uuid,
3102                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3103            ),
3104            (Attribute::Description, Value::new_utf8s("testperson1")),
3105            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3106        );
3107        let ce = CreateEvent::new_internal(vec![e1]);
3108        let cr = server_txn.create(&ce);
3109        assert!(cr.is_ok());
3110
3111        // Name doesn't exist
3112        let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3113        // There is nothing.
3114        assert_eq!(r1, Ok(None));
3115        // Name does exist
3116        let r3 = server_txn.uuid_to_spn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3117        println!("{r3:?}");
3118        assert_eq!(
3119            r3.unwrap().unwrap(),
3120            Value::new_spn_str("testperson1", "example.com")
3121        );
3122        // Name is not syntax normalised (but exists)
3123        let r4 = server_txn.uuid_to_spn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3124        assert_eq!(
3125            r4.unwrap().unwrap(),
3126            Value::new_spn_str("testperson1", "example.com")
3127        );
3128    }
3129
3130    #[qs_test]
3131    async fn test_uuid_to_rdn(server: &QueryServer) {
3132        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3133
3134        let e1 = entry_init!(
3135            (Attribute::Class, EntryClass::Object.to_value()),
3136            (Attribute::Class, EntryClass::Person.to_value()),
3137            (Attribute::Class, EntryClass::Account.to_value()),
3138            (Attribute::Name, Value::new_iname("testperson1")),
3139            (
3140                Attribute::Uuid,
3141                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3142            ),
3143            (Attribute::Description, Value::new_utf8s("testperson")),
3144            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3145        );
3146        let ce = CreateEvent::new_internal(vec![e1]);
3147        let cr = server_txn.create(&ce);
3148        assert!(cr.is_ok());
3149
3150        // Name doesn't exist
3151        let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3152        // There is nothing.
3153        assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
3154        // Name does exist
3155        let r3 = server_txn.uuid_to_rdn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
3156        println!("{r3:?}");
3157        assert_eq!(r3.unwrap(), "spn=testperson1@example.com");
3158        // Uuid is not syntax normalised (but exists)
3159        let r4 = server_txn.uuid_to_rdn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
3160        assert_eq!(r4.unwrap(), "spn=testperson1@example.com");
3161    }
3162
3163    #[qs_test]
3164    async fn test_clone_value(server: &QueryServer) {
3165        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3166        let e1 = entry_init!(
3167            (Attribute::Class, EntryClass::Object.to_value()),
3168            (Attribute::Class, EntryClass::Account.to_value()),
3169            (Attribute::Class, EntryClass::Person.to_value()),
3170            (Attribute::Name, Value::new_iname("testperson1")),
3171            (
3172                Attribute::Uuid,
3173                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3174            ),
3175            (Attribute::Description, Value::new_utf8s("testperson1")),
3176            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
3177        );
3178        let ce = CreateEvent::new_internal(vec![e1]);
3179        let cr = server_txn.create(&ce);
3180        assert!(cr.is_ok());
3181
3182        // test attr not exist
3183        let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
3184
3185        assert!(r1.is_err());
3186
3187        // test attr not-normalised (error)
3188        // test attr not-reference
3189        let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
3190
3191        assert!(r2.is_err());
3192
3193        // test attr reference
3194        let r3 = server_txn.clone_value(&Attribute::from("member"), "testperson1");
3195
3196        assert_eq!(
3197            r3,
3198            Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3199        );
3200
3201        // test attr reference already resolved.
3202        let r4 = server_txn.clone_value(
3203            &Attribute::from("member"),
3204            "cc8e95b4-c24f-4d68-ba54-8bed76f63930",
3205        );
3206
3207        debug!("{:?}", r4);
3208        assert_eq!(
3209            r4,
3210            Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
3211        );
3212    }
3213
3214    #[qs_test]
3215    async fn test_dynamic_schema_class(server: &QueryServer) {
3216        let e1 = entry_init!(
3217            (Attribute::Class, EntryClass::Object.to_value()),
3218            (Attribute::Class, EntryClass::TestClass.to_value()),
3219            (Attribute::Name, Value::new_iname("testobj1")),
3220            (
3221                Attribute::Uuid,
3222                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3223            )
3224        );
3225
3226        // Class definition
3227        let e_cd = entry_init!(
3228            (Attribute::Class, EntryClass::Object.to_value()),
3229            (Attribute::Class, EntryClass::ClassType.to_value()),
3230            (Attribute::ClassName, EntryClass::TestClass.to_value()),
3231            (
3232                Attribute::Uuid,
3233                Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3234            ),
3235            (Attribute::Description, Value::new_utf8s("Test Class")),
3236            (Attribute::May, Value::from(Attribute::Name))
3237        );
3238        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3239        // Add a new class.
3240        let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
3241        assert!(server_txn.create(&ce_class).is_ok());
3242        // Trying to add it now should fail.
3243        let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3244        assert!(server_txn.create(&ce_fail).is_err());
3245
3246        // Commit
3247        server_txn.commit().expect("should not fail");
3248
3249        // Start a new write
3250        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3251        // Add the class to an object
3252        // should work
3253        let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3254        assert!(server_txn.create(&ce_work).is_ok());
3255
3256        // Commit
3257        server_txn.commit().expect("should not fail");
3258
3259        // Start a new write
3260        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3261        // delete the class
3262        let de_class = DeleteEvent::new_internal_invalid(filter!(f_eq(
3263            Attribute::ClassName,
3264            EntryClass::TestClass.into()
3265        )));
3266        assert!(server_txn.delete(&de_class).is_ok());
3267        // Commit
3268        server_txn.commit().expect("should not fail");
3269
3270        // Start a new write
3271        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3272        // Trying to add now should fail
3273        let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3274        assert!(server_txn.create(&ce_fail).is_err());
3275        // Search our entry
3276        let testobj1 = server_txn
3277            .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3278            .expect("failed");
3279        assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into()));
3280
3281        // Should still be good
3282        server_txn.commit().expect("should not fail");
3283        // Commit.
3284    }
3285
3286    #[qs_test]
3287    async fn test_dynamic_schema_attr(server: &QueryServer) {
3288        let e1 = entry_init!(
3289            (Attribute::Class, EntryClass::Object.to_value()),
3290            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3291            (Attribute::Name, Value::new_iname("testobj1")),
3292            (
3293                Attribute::Uuid,
3294                Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3295            ),
3296            (Attribute::TestAttr, Value::new_utf8s("test"))
3297        );
3298
3299        // Attribute definition
3300        let e_ad = entry_init!(
3301            (Attribute::Class, EntryClass::Object.to_value()),
3302            (Attribute::Class, EntryClass::AttributeType.to_value()),
3303            (
3304                Attribute::Uuid,
3305                Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3306            ),
3307            (Attribute::AttributeName, Value::from(Attribute::TestAttr)),
3308            (Attribute::Description, Value::new_utf8s("Test Attribute")),
3309            (Attribute::MultiValue, Value::new_bool(false)),
3310            (Attribute::Unique, Value::new_bool(false)),
3311            (
3312                Attribute::Syntax,
3313                Value::new_syntaxs("UTF8STRING").expect("syntax")
3314            )
3315        );
3316
3317        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3318        // Add a new attribute.
3319        let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
3320        assert!(server_txn.create(&ce_attr).is_ok());
3321        // Trying to add it now should fail. (use extensible object)
3322        let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3323        assert!(server_txn.create(&ce_fail).is_err());
3324
3325        // Commit
3326        server_txn.commit().expect("should not fail");
3327
3328        // Start a new write
3329        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3330        // Add the attr to an object
3331        // should work
3332        let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3333        assert!(server_txn.create(&ce_work).is_ok());
3334
3335        // Commit
3336        server_txn.commit().expect("should not fail");
3337
3338        // Start a new write
3339        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3340        // delete the attr
3341        let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
3342            Attribute::AttributeName,
3343            PartialValue::from(Attribute::TestAttr)
3344        )));
3345        assert!(server_txn.delete(&de_attr).is_ok());
3346        // Commit
3347        server_txn.commit().expect("should not fail");
3348
3349        // Start a new write
3350        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3351        // Trying to add now should fail
3352        let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3353        assert!(server_txn.create(&ce_fail).is_err());
3354        // Search our attribute - should FAIL
3355        let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
3356        assert!(server_txn.internal_search(filt).is_err());
3357        // Search the entry - the attribute will still be present
3358        // even if we can't search on it.
3359        let testobj1 = server_txn
3360            .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3361            .expect("failed");
3362        assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test")));
3363
3364        server_txn.commit().expect("should not fail");
3365        // Commit.
3366    }
3367
3368    #[qs_test]
3369    async fn test_scim_entry_structure(server: &QueryServer) {
3370        let mut read_txn = server.read().await.unwrap();
3371
3372        // Query entry (A builtin one ?)
3373        let entry = read_txn
3374            .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
3375            .unwrap();
3376
3377        // Convert entry into scim
3378        let reduced = entry.as_ref().clone().into_reduced();
3379        let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
3380
3381        // Assert scim entry attributes are as expected
3382        assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE);
3383        let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap();
3384        match name_scim {
3385            ScimValueKanidm::String(name) => {
3386                assert_eq!(name.clone(), "idm_people_self_name_write")
3387            }
3388            _ => {
3389                panic!("expected String, actual {name_scim:?}");
3390            }
3391        }
3392
3393        // such as returning a new struct type for `members` attributes or `managed_by`
3394        let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap();
3395        match entry_managed_by_scim {
3396            ScimValueKanidm::EntryReferences(managed_by) => {
3397                assert_eq!(
3398                    managed_by.first().unwrap().clone(),
3399                    ScimReference {
3400                        uuid: UUID_IDM_ADMINS,
3401                        value: "idm_admins@example.com".to_string()
3402                    }
3403                )
3404            }
3405            _ => {
3406                panic!("expected EntryReference, actual {entry_managed_by_scim:?}");
3407            }
3408        }
3409
3410        let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap();
3411        match members_scim {
3412            ScimValueKanidm::EntryReferences(members) => {
3413                assert_eq!(
3414                    members.first().unwrap().clone(),
3415                    ScimReference {
3416                        uuid: UUID_IDM_ALL_PERSONS,
3417                        value: "idm_all_persons@example.com".to_string()
3418                    }
3419                )
3420            }
3421            _ => {
3422                panic!("expected EntryReferences, actual {members_scim:?}");
3423            }
3424        }
3425    }
3426
3427    #[qs_test]
3428    async fn test_scim_effective_access_query(server: &QueryServer) {
3429        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3430
3431        let group_uuid = Uuid::new_v4();
3432        let e1 = entry_init!(
3433            (Attribute::Class, EntryClass::Object.to_value()),
3434            (Attribute::Class, EntryClass::Group.to_value()),
3435            (Attribute::Name, Value::new_iname("testgroup")),
3436            (Attribute::Uuid, Value::Uuid(group_uuid))
3437        );
3438
3439        assert!(server_txn.internal_create(vec![e1]).is_ok());
3440        assert!(server_txn.commit().is_ok());
3441
3442        // Now read that entry.
3443
3444        let mut server_txn = server.read().await.unwrap();
3445
3446        let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3447        let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3448
3449        let query = ScimEntryGetQuery {
3450            ext_access_check: true,
3451            ..Default::default()
3452        };
3453
3454        let scim_entry = server_txn
3455            .scim_entry_id_get_ext(group_uuid, EntryClass::Group, query, idm_admin_ident)
3456            .unwrap();
3457
3458        let ext_access_check = scim_entry.ext_access_check.unwrap();
3459
3460        trace!(?ext_access_check);
3461
3462        assert!(ext_access_check.delete);
3463        assert!(ext_access_check.search.check(&Attribute::DirectMemberOf));
3464        assert!(ext_access_check.search.check(&Attribute::MemberOf));
3465        assert!(ext_access_check.search.check(&Attribute::Name));
3466        assert!(ext_access_check.modify_present.check(&Attribute::Name));
3467        assert!(ext_access_check.modify_remove.check(&Attribute::Name));
3468    }
3469
3470    #[qs_test]
3471    async fn test_scim_basic_search_ext_query(server: &QueryServer) {
3472        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3473
3474        let group_uuid = Uuid::new_v4();
3475        let e1 = entry_init!(
3476            (Attribute::Class, EntryClass::Object.to_value()),
3477            (Attribute::Class, EntryClass::Group.to_value()),
3478            (Attribute::Name, Value::new_iname("testgroup")),
3479            (Attribute::Uuid, Value::Uuid(group_uuid))
3480        );
3481
3482        assert!(server_txn.internal_create(vec![e1]).is_ok());
3483        assert!(server_txn.commit().is_ok());
3484
3485        // Now read that entry.
3486        let mut server_txn = server.read().await.unwrap();
3487
3488        let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3489        let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3490
3491        let filter = ScimFilter::And(
3492            Box::new(ScimFilter::Equal(
3493                Attribute::Class.into(),
3494                EntryClass::Group.into(),
3495            )),
3496            Box::new(ScimFilter::Equal(
3497                Attribute::Uuid.into(),
3498                JsonValue::String(group_uuid.to_string()),
3499            )),
3500        );
3501
3502        let base: ScimListResponse = server_txn
3503            .scim_search_ext(idm_admin_ident, filter, ScimEntryGetQuery::default())
3504            .unwrap();
3505
3506        assert_eq!(base.resources.len(), 1);
3507        assert_eq!(base.total_results, 1);
3508        // Pagination not requested,
3509        assert_eq!(base.items_per_page, None);
3510        assert_eq!(base.start_index, None);
3511        assert_eq!(base.resources[0].header.id, group_uuid);
3512    }
3513
3514    #[qs_test]
3515    async fn test_scim_basic_search_ext_query_with_sort(server: &QueryServer) {
3516        let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3517
3518        for i in (1..4).rev() {
3519            let e1 = entry_init!(
3520                (Attribute::Class, EntryClass::Object.to_value()),
3521                (Attribute::Class, EntryClass::Group.to_value()),
3522                (
3523                    Attribute::Name,
3524                    Value::new_iname(format!("testgroup{i}").as_str())
3525                )
3526            );
3527            assert!(server_txn.internal_create(vec![e1]).is_ok());
3528        }
3529
3530        assert!(server_txn.commit().is_ok());
3531
3532        // Now read that entry.
3533        let mut server_txn = server.read().await.unwrap();
3534
3535        let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3536        let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3537
3538        let filter = ScimFilter::And(
3539            Box::new(ScimFilter::Equal(
3540                Attribute::Class.into(),
3541                EntryClass::Group.into(),
3542            )),
3543            Box::new(ScimFilter::StartsWith(
3544                Attribute::Name.into(),
3545                JsonValue::String("testgroup".into()),
3546            )),
3547        );
3548
3549        let base: ScimListResponse = server_txn
3550            .scim_search_ext(
3551                idm_admin_ident.clone(),
3552                filter.clone(),
3553                ScimEntryGetQuery {
3554                    sort_by: Some(Attribute::Name),
3555                    ..Default::default()
3556                },
3557            )
3558            .unwrap();
3559
3560        assert_eq!(base.resources.len(), 3);
3561        assert_eq!(base.total_results, 3);
3562        // Pagination not requested,
3563        assert_eq!(base.items_per_page, None);
3564        assert_eq!(base.start_index, None);
3565
3566        let Some(ScimValueKanidm::String(testgroup_name_0)) =
3567            base.resources[0].attrs.get(&Attribute::Name)
3568        else {
3569            panic!("Invalid data in attribute.");
3570        };
3571        let Some(ScimValueKanidm::String(testgroup_name_1)) =
3572            base.resources[1].attrs.get(&Attribute::Name)
3573        else {
3574            panic!("Invalid data in attribute.");
3575        };
3576        let Some(ScimValueKanidm::String(testgroup_name_2)) =
3577            base.resources[2].attrs.get(&Attribute::Name)
3578        else {
3579            panic!("Invalid data in attribute.");
3580        };
3581
3582        assert!(testgroup_name_0 < testgroup_name_1);
3583        assert!(testgroup_name_0 < testgroup_name_2);
3584        assert!(testgroup_name_1 < testgroup_name_2);
3585
3586        // ================
3587        // Test pagination.
3588        let base: ScimListResponse = server_txn
3589            .scim_search_ext(
3590                idm_admin_ident.clone(),
3591                filter.clone(),
3592                ScimEntryGetQuery {
3593                    count: NonZeroU64::new(1),
3594                    ..Default::default()
3595                },
3596            )
3597            .unwrap();
3598
3599        assert_eq!(base.resources.len(), 1);
3600        assert_eq!(base.total_results, 3);
3601        // Pagination not requested,
3602        assert_eq!(base.items_per_page, NonZeroU64::new(1));
3603        assert_eq!(base.start_index, NonZeroU64::new(1));
3604
3605        let Some(ScimValueKanidm::String(testgroup_name_0)) =
3606            base.resources[0].attrs.get(&Attribute::Name)
3607        else {
3608            panic!("Invalid data in attribute.");
3609        };
3610        // DB has reverse order
3611        assert_eq!(testgroup_name_0, "testgroup3");
3612
3613        // ================
3614        // Test pagination + sort
3615        let base: ScimListResponse = server_txn
3616            .scim_search_ext(
3617                idm_admin_ident,
3618                filter.clone(),
3619                ScimEntryGetQuery {
3620                    sort_by: Some(Attribute::Name),
3621                    count: NonZeroU64::new(2),
3622                    start_index: NonZeroU64::new(2),
3623                    ..Default::default()
3624                },
3625            )
3626            .unwrap();
3627
3628        assert_eq!(base.resources.len(), 2);
3629        assert_eq!(base.total_results, 3);
3630        assert_eq!(base.items_per_page, NonZeroU64::new(2));
3631        assert_eq!(base.start_index, NonZeroU64::new(2));
3632
3633        let Some(ScimValueKanidm::String(testgroup_name_0)) =
3634            base.resources[0].attrs.get(&Attribute::Name)
3635        else {
3636            panic!("Invalid data in attribute.");
3637        };
3638        let Some(ScimValueKanidm::String(testgroup_name_1)) =
3639            base.resources[1].attrs.get(&Attribute::Name)
3640        else {
3641            panic!("Invalid data in attribute.");
3642        };
3643        // Sorted, note we skipped entry "testgroup 1" using pagination.
3644        assert_eq!(testgroup_name_0, "testgroup2");
3645        assert_eq!(testgroup_name_1, "testgroup3");
3646    }
3647}