1use self::access::{
5 profiles::{
6 AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
7 },
8 AccessControls, AccessControlsReadTransaction, AccessControlsTransaction,
9 AccessControlsWriteTransaction,
10};
11use self::keys::{
12 KeyObject, KeyProvider, KeyProviders, KeyProvidersReadTransaction, KeyProvidersTransaction,
13 KeyProvidersWriteTransaction,
14};
15use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction};
16use crate::filter::{
17 Filter, FilterInvalid, FilterValid, FilterValidResolved, ResolveFilterCache,
18 ResolveFilterCacheReadTxn,
19};
20use crate::plugins::{
21 self,
22 dyngroup::{DynGroup, DynGroupCache},
23 Plugins,
24};
25use crate::prelude::*;
26use crate::repl::cid::Cid;
27use crate::repl::proto::ReplRuvRange;
28use crate::repl::ruv::ReplicationUpdateVectorTransaction;
29use crate::schema::{
30 Schema, SchemaAttribute, SchemaClass, SchemaReadTransaction, SchemaTransaction,
31 SchemaWriteTransaction,
32};
33use crate::value::{CredentialType, EXTRACT_VAL_DN};
34use crate::valueset::*;
35use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
36use concread::cowcell::*;
37use crypto_glue::{hmac_s256::HmacSha256Key, s256::Sha256Output};
38use hashbrown::{HashMap, HashSet};
39use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
40use kanidm_proto::scim_v1::{
41 server::{ScimListResponse, ScimOAuth2ClaimMap, ScimOAuth2ScopeMap, ScimReference},
42 JsonValue, ScimEntryGetQuery, ScimFilter,
43};
44use std::collections::{BTreeMap, BTreeSet};
45use std::num::NonZeroU64;
46use std::str::FromStr;
47use std::sync::Arc;
48use time::OffsetDateTime;
49use tokio::sync::{Semaphore, SemaphorePermit};
50use tracing::trace;
51
52pub(crate) mod access;
53pub mod assert;
54pub mod batch_modify;
55pub mod create;
56pub mod delete;
57pub mod identity;
58pub(crate) mod keys;
59pub(crate) mod migrations;
60pub mod modify;
61pub(crate) mod recycle;
62pub mod scim;
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#[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 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 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 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 pub(super) changed_flags: ChangeFlag,
236
237 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
264pub 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 fn get_resolve_filter_cache_and_be_txn(
318 &mut self,
319 ) -> (
320 &mut Self::BackendTransactionType,
321 Option<&mut ResolveFilterCacheReadTxn<'a>>,
322 );
323
324 #[instrument(level = "debug", skip_all)]
334 fn search_ext(
335 &mut self,
336 se: &SearchEvent,
337 ) -> Result<Vec<EntryReducedCommitted>, OperationError> {
338 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 admin_error!(?e, "Failed to filter entry attributes");
351 e
352 })
353 }
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 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 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 let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
399 admin_error!(?e, "backend failure");
400 OperationError::Backend
401 })?;
402
403 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 be_txn.exists(lims, &vfr).map_err(|e| {
434 admin_error!(?e, "backend failure");
435 OperationError::Backend
436 })
437 } else {
438 let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
441 admin_error!(?e, "backend failure");
442 OperationError::Backend
443 })?;
444
445 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 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 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 fn sync_external_id_to_uuid(
495 &mut self,
496 external_id: &str,
497 ) -> Result<Option<Uuid>, OperationError> {
498 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 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 self.get_be_txn()
520 .uuid2rdn(uuid)
521 .map(|v| v.unwrap_or_else(|| format!("uuid={}", uuid.as_hyphenated())))
522 }
523
524 #[instrument(level = "debug", skip_all)]
526 fn internal_exists(&mut self, filter: &Filter<FilterInvalid>) -> Result<bool, OperationError> {
527 let f_valid = filter
529 .validate(self.get_schema())
530 .map_err(OperationError::SchemaViolation)?;
531 let ee = ExistsEvent::new_internal(f_valid);
533 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 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 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 #[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 #[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 #[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 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 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 fn clone_value(&mut self, attr: &Attribute, value: &str) -> Result<Value, OperationError> {
724 let schema = self.get_schema();
725
726 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 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 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 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 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 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 let Some(schema_a) = schema.get_attributes().get(attr) else {
1053 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 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 } 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 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 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 let domain_uuid = self.get_domain_uuid();
1390
1391 let ruv_snapshot = self.get_be_txn().get_ruv();
1394
1395 ruv_snapshot
1397 .current_ruv_range()
1398 .map(|ranges| ReplRuvRange::V1 {
1399 domain_uuid,
1400 ranges,
1401 })
1402 }
1403}
1404
1405impl<'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 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 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 pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
1523 let be_errs = self.get_be_txn().verify();
1527
1528 if !be_errs.is_empty() {
1529 return be_errs;
1530 }
1531
1532 let sc_errs = self.get_schema().validate();
1534
1535 if !sc_errs.is_empty() {
1536 return sc_errs;
1537 }
1538
1539 let idx_errs = self.get_be_txn().verify_indexes();
1543
1544 if !idx_errs.is_empty() {
1545 return idx_errs;
1546 }
1547
1548 let mut results = Vec::with_capacity(0);
1551
1552 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 self.get_be_txn().verify_ruv(&mut results);
1568
1569 Plugins::run_verify(self, &mut results);
1575 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 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 let total_results = result_set.len() as u64;
1671
1672 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 let (items_per_page, start_index, paginated_result_set) = if let Some(count) = query.count {
1692 let count: u64 = count.get();
1693 let start_index: u64 = query
1696 .start_index
1697 .map(|non_zero_index|
1698 non_zero_index.get() - 1)
1700 .unwrap_or_default();
1701
1702 if start_index as usize > result_set.len() {
1704 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 (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 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 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 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 d_vers: DOMAIN_LEVEL_0,
1887 d_patch_level: 0,
1888 d_name: domain_name.clone(),
1889 d_display: domain_name,
1892 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 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 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 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 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 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 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 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 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 *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 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 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 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 if level > mut_d_info.d_vers {
2240 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 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 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 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 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 let valid_r = self.schema.validate();
2303
2304 if !valid_r.is_empty() {
2306 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 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 self.resolve_filter_cache_clear = true;
2334
2335 DynGroup::reload(self)?;
2338
2339 Ok(())
2340 }
2341
2342 #[instrument(level = "debug", skip_all)]
2343 fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
2344 trace!("ACP reload started ...");
2352
2353 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 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 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 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 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 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 #[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 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 .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 let mut_d_info = self.d_info.get_mut();
2581 debug!(?mut_d_info);
2582 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 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 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 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 if previous_version <= DOMAIN_LEVEL_10 && domain_info_version >= DOMAIN_LEVEL_11 {
2650 self.migrate_domain_10_to_11()?;
2652 }
2653
2654 if previous_version <= DOMAIN_LEVEL_11 && domain_info_version >= DOMAIN_LEVEL_12 {
2655 self.migrate_domain_11_to_12()?;
2657 }
2658
2659 if previous_version <= DOMAIN_LEVEL_12 && domain_info_version >= DOMAIN_LEVEL_13 {
2660 self.migrate_domain_12_to_13()?;
2662 }
2663
2664 if previous_version <= DOMAIN_LEVEL_13 && domain_info_version >= DOMAIN_LEVEL_14 {
2665 self.migrate_domain_13_to_14()?;
2667 }
2668
2669 if previous_version <= DOMAIN_LEVEL_14 && domain_info_version >= DOMAIN_LEVEL_1_11 {
2670 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 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 self.migrate_domain_1_12_to_1_13()?;
2682 }
2683
2684 const { assert!(DOMAIN_MAX_LEVEL == DOMAIN_LEVEL_1_13) };
2688 debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
2689
2690 Ok(())
2691 }
2692
2693 #[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 #[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 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 }
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 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 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 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 pub(crate) fn set_phase_bootstrap(&mut self) {
2903 *self.phase = ServerPhase::Bootstrap;
2904 }
2905
2906 pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
2908 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 if self.changed_flags.intersects(ChangeFlag::DOMAIN) {
2922 self.reload_domain_info_version()?;
2923 }
2924
2925 if self.changed_flags.intersects(ChangeFlag::SCHEMA) {
2930 self.reload_schema()?;
2931
2932 if *self.phase >= ServerPhase::Running {
2936 self.reindex(false)?;
2937 self.reload_schema()?;
2938 }
2939 }
2940
2941 if self
2944 .changed_flags
2945 .intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
2946 {
2947 self.reload_key_material()?;
2948 }
2949
2950 if self
2958 .changed_flags
2959 .intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
2960 {
2961 self.reload_accesscontrols()?;
2962 } else {
2963 }
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 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 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 _db_ticket,
3023 _write_ticket,
3024 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 trace!(
3038 changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
3039 );
3040
3041 be_txn.set_db_ts_max(cid.ts)?;
3044 cid.commit();
3045
3046 if resolve_filter_cache_clear {
3048 resolve_filter_cache_write.clear();
3049 }
3050 resolve_filter_cache_write.commit();
3051
3052 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 let r1 = server_txn.name_to_uuid("testpers");
3100 assert!(r1.is_err());
3101 let r2 = server_txn.name_to_uuid("tEsTpErS");
3103 assert!(r2.is_err());
3104 let r3 = server_txn.name_to_uuid("testperson1");
3106 assert_eq!(r3, Ok(t_uuid));
3107 let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
3109 assert_eq!(r4, Ok(t_uuid));
3110 let r5 = server_txn.name_to_uuid("name=testperson1");
3112 assert_eq!(r5, Ok(t_uuid));
3113 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 let r1 = server_txn.sync_external_id_to_uuid("tobias");
3137 assert_eq!(r1, Ok(None));
3138 let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
3140 assert_eq!(r2, Ok(None));
3141 let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
3143 assert_eq!(r3, Ok(Some(t_uuid)));
3144 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 let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3171 assert_eq!(r1, Ok(None));
3173 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 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 let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
3210 assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
3212 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 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 let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
3242
3243 assert!(r1.is_err());
3244
3245 let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
3248
3249 assert!(r2.is_err());
3250
3251 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 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 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 let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
3299 assert!(server_txn.create(&ce_class).is_ok());
3300 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3302 assert!(server_txn.create(&ce_fail).is_err());
3303
3304 server_txn.commit().expect("should not fail");
3306
3307 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3309 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3312 assert!(server_txn.create(&ce_work).is_ok());
3313
3314 server_txn.commit().expect("should not fail");
3316
3317 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3319 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 server_txn.commit().expect("should not fail");
3327
3328 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3330 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3332 assert!(server_txn.create(&ce_fail).is_err());
3333 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 server_txn.commit().expect("should not fail");
3341 }
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 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 let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
3378 assert!(server_txn.create(&ce_attr).is_ok());
3379 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3381 assert!(server_txn.create(&ce_fail).is_err());
3382
3383 server_txn.commit().expect("should not fail");
3385
3386 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3388 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3391 assert!(server_txn.create(&ce_work).is_ok());
3392
3393 server_txn.commit().expect("should not fail");
3395
3396 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3398 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 server_txn.commit().expect("should not fail");
3406
3407 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3409 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3411 assert!(server_txn.create(&ce_fail).is_err());
3412 let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
3414 assert!(server_txn.internal_search(filt).is_err());
3415 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 }
3425
3426 #[qs_test]
3427 async fn test_scim_entry_structure(server: &QueryServer) {
3428 let mut read_txn = server.read().await.unwrap();
3429
3430 let entry = read_txn
3432 .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
3433 .unwrap();
3434
3435 let reduced = entry.as_ref().clone().into_reduced();
3437 let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
3438
3439 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 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 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 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 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 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 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 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 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 assert_eq!(testgroup_name_0, "testgroup3");
3670
3671 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 assert_eq!(testgroup_name_0, "testgroup2");
3703 assert_eq!(testgroup_name_1, "testgroup3");
3704 }
3705}