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