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::dyngroup::{DynGroup, DynGroupCache};
21use crate::plugins::Plugins;
22use crate::prelude::*;
23use crate::repl::cid::Cid;
24use crate::repl::proto::ReplRuvRange;
25use crate::repl::ruv::ReplicationUpdateVectorTransaction;
26use crate::schema::{
27 Schema, SchemaAttribute, SchemaClass, SchemaReadTransaction, SchemaTransaction,
28 SchemaWriteTransaction,
29};
30use crate::value::{CredentialType, EXTRACT_VAL_DN};
31use crate::valueset::*;
32use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
33use concread::cowcell::*;
34use hashbrown::{HashMap, HashSet};
35use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
36use kanidm_proto::scim_v1::{
37 server::{ScimListResponse, ScimOAuth2ClaimMap, ScimOAuth2ScopeMap, ScimReference},
38 JsonValue, ScimEntryGetQuery, ScimFilter,
39};
40use std::collections::BTreeSet;
41use std::num::NonZeroU64;
42use std::str::FromStr;
43use std::sync::Arc;
44use tokio::sync::{Semaphore, SemaphorePermit};
45use tracing::trace;
46
47pub(crate) mod access;
48pub mod batch_modify;
49pub mod create;
50pub mod delete;
51pub mod identity;
52pub(crate) mod keys;
53pub(crate) mod migrations;
54pub mod modify;
55pub(crate) mod recycle;
56pub mod scim;
57
58const RESOLVE_FILTER_CACHE_MAX: usize = 256;
59const RESOLVE_FILTER_CACHE_LOCAL: usize = 8;
60
61#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq)]
62pub(crate) enum ServerPhase {
63 Bootstrap,
64 SchemaReady,
65 DomainInfoReady,
66 Running,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct DomainInfo {
73 pub(crate) d_uuid: Uuid,
74 pub(crate) d_name: String,
75 pub(crate) d_display: String,
76 pub(crate) d_vers: DomainVersion,
77 pub(crate) d_patch_level: u32,
78 pub(crate) d_devel_taint: bool,
79 pub(crate) d_ldap_allow_unix_pw_bind: bool,
80 pub(crate) d_allow_easter_eggs: bool,
81 d_image: Option<ImageValue>,
83}
84
85impl DomainInfo {
86 pub fn name(&self) -> &str {
87 self.d_name.as_str()
88 }
89
90 pub fn display_name(&self) -> &str {
91 self.d_display.as_str()
92 }
93
94 pub fn devel_taint(&self) -> bool {
95 self.d_devel_taint
96 }
97
98 pub fn image(&self) -> Option<&ImageValue> {
99 self.d_image.as_ref()
100 }
101
102 pub fn has_custom_image(&self) -> bool {
103 self.d_image.is_some()
104 }
105
106 pub fn allow_easter_eggs(&self) -> bool {
107 self.d_allow_easter_eggs
108 }
109
110 #[cfg(feature = "test")]
111 pub fn new_test() -> CowCell<Self> {
112 concread::cowcell::CowCell::new(Self {
113 d_uuid: Uuid::new_v4(),
114 d_name: "test domain".to_string(),
115 d_display: "Test Domain".to_string(),
116 d_vers: 1,
117 d_patch_level: 0,
118 d_devel_taint: false,
119 d_ldap_allow_unix_pw_bind: false,
120 d_allow_easter_eggs: false,
121 d_image: None,
122 })
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Default)]
127pub struct SystemConfig {
128 pub(crate) denied_names: HashSet<String>,
129 pub(crate) pw_badlist: HashSet<String>,
130}
131
132#[derive(Clone)]
133pub struct QueryServer {
134 phase: Arc<CowCell<ServerPhase>>,
135 pub(crate) d_info: Arc<CowCell<DomainInfo>>,
136 system_config: Arc<CowCell<SystemConfig>>,
137 be: Backend,
138 schema: Arc<Schema>,
139 accesscontrols: Arc<AccessControls>,
140 db_tickets: Arc<Semaphore>,
141 read_tickets: Arc<Semaphore>,
142 write_ticket: Arc<Semaphore>,
143 resolve_filter_cache: Arc<ResolveFilterCache>,
144 dyngroup_cache: Arc<CowCell<DynGroupCache>>,
145 cid_max: Arc<CowCell<Cid>>,
146 key_providers: Arc<KeyProviders>,
147}
148
149pub struct QueryServerReadTransaction<'a> {
150 be_txn: BackendReadTransaction<'a>,
151 pub(crate) d_info: CowCellReadTxn<DomainInfo>,
154 system_config: CowCellReadTxn<SystemConfig>,
155 schema: SchemaReadTransaction,
156 accesscontrols: AccessControlsReadTransaction<'a>,
157 key_providers: KeyProvidersReadTransaction,
158 _db_ticket: SemaphorePermit<'a>,
159 _read_ticket: SemaphorePermit<'a>,
160 resolve_filter_cache: ResolveFilterCacheReadTxn<'a>,
161 trim_cid: Cid,
164}
165
166unsafe impl Sync for QueryServerReadTransaction<'_> {}
167
168unsafe impl Send for QueryServerReadTransaction<'_> {}
169
170bitflags::bitflags! {
171 #[derive(Copy, Clone, Debug)]
172 pub struct ChangeFlag: u32 {
173 const SCHEMA = 0b0000_0001;
174 const ACP = 0b0000_0010;
175 const OAUTH2 = 0b0000_0100;
176 const DOMAIN = 0b0000_1000;
177 const SYSTEM_CONFIG = 0b0001_0000;
178 const SYNC_AGREEMENT = 0b0010_0000;
179 const KEY_MATERIAL = 0b0100_0000;
180 const APPLICATION = 0b1000_0000;
181 }
182}
183
184pub struct QueryServerWriteTransaction<'a> {
185 committed: bool,
186 phase: CowCellWriteTxn<'a, ServerPhase>,
187 d_info: CowCellWriteTxn<'a, DomainInfo>,
188 system_config: CowCellWriteTxn<'a, SystemConfig>,
189 curtime: Duration,
190 cid: CowCellWriteTxn<'a, Cid>,
191 trim_cid: Cid,
192 pub(crate) be_txn: BackendWriteTransaction<'a>,
193 pub(crate) schema: SchemaWriteTransaction<'a>,
194 accesscontrols: AccessControlsWriteTransaction<'a>,
195 key_providers: KeyProvidersWriteTransaction<'a>,
196 pub(super) changed_flags: ChangeFlag,
200
201 pub(super) changed_uuid: HashSet<Uuid>,
203 _db_ticket: SemaphorePermit<'a>,
204 _write_ticket: SemaphorePermit<'a>,
205 resolve_filter_cache_clear: bool,
206 resolve_filter_cache_write: ARCacheWriteTxn<
207 'a,
208 (IdentityId, Arc<Filter<FilterValid>>),
209 Arc<Filter<FilterValidResolved>>,
210 (),
211 >,
212 resolve_filter_cache: ARCacheReadTxn<
213 'a,
214 (IdentityId, Arc<Filter<FilterValid>>),
215 Arc<Filter<FilterValidResolved>>,
216 (),
217 >,
218 dyngroup_cache: CowCellWriteTxn<'a, DynGroupCache>,
219}
220
221impl QueryServerWriteTransaction<'_> {
222 pub(crate) fn trim_cid(&self) -> &Cid {
223 &self.trim_cid
224 }
225}
226
227pub trait QueryServerTransaction<'a> {
237 type BackendTransactionType: BackendTransaction;
238 fn get_be_txn(&mut self) -> &mut Self::BackendTransactionType;
239
240 type SchemaTransactionType: SchemaTransaction;
241 fn get_schema<'b>(&self) -> &'b Self::SchemaTransactionType;
242
243 type AccessControlsTransactionType: AccessControlsTransaction<'a>;
244 fn get_accesscontrols(&self) -> &Self::AccessControlsTransactionType;
245
246 type KeyProvidersTransactionType: KeyProvidersTransaction;
247 fn get_key_providers(&self) -> &Self::KeyProvidersTransactionType;
248
249 fn pw_badlist(&self) -> &HashSet<String>;
250
251 fn denied_names(&self) -> &HashSet<String>;
252
253 fn get_domain_version(&self) -> DomainVersion;
254
255 fn get_domain_patch_level(&self) -> u32;
256
257 fn get_domain_development_taint(&self) -> bool;
258
259 fn get_domain_uuid(&self) -> Uuid;
260
261 fn get_domain_name(&self) -> &str;
262
263 fn get_domain_display_name(&self) -> &str;
264
265 fn get_domain_image_value(&self) -> Option<ImageValue>;
266
267 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>>;
268
269 fn get_resolve_filter_cache_and_be_txn(
273 &mut self,
274 ) -> (
275 &mut Self::BackendTransactionType,
276 Option<&mut ResolveFilterCacheReadTxn<'a>>,
277 );
278
279 #[instrument(level = "debug", skip_all)]
289 fn search_ext(
290 &mut self,
291 se: &SearchEvent,
292 ) -> Result<Vec<EntryReducedCommitted>, OperationError> {
293 let entries = self.search(se)?;
299
300 let access = self.get_accesscontrols();
301 access
302 .search_filter_entry_attributes(se, entries)
303 .map_err(|e| {
304 admin_error!(?e, "Failed to filter entry attributes");
306 e
307 })
308 }
310
311 #[instrument(level = "debug", skip_all)]
312 fn search(
313 &mut self,
314 se: &SearchEvent,
315 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
316 if se.ident.is_internal() {
317 trace!(internal_filter = ?se.filter, "search");
318 } else {
319 security_info!(initiator = %se.ident, "search");
320 admin_debug!(external_filter = ?se.filter, "search");
321 }
322
323 let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
333
334 let idxmeta = be_txn.get_idxmeta_ref();
335
336 trace!(resolve_filter_cache = %resolve_filter_cache.is_some());
337
338 let vfr = se
340 .filter
341 .resolve(&se.ident, Some(idxmeta), resolve_filter_cache)
342 .map_err(|e| {
343 admin_error!(?e, "search filter resolve failure");
344 e
345 })?;
346
347 let lims = se.ident.limits();
348
349 let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
354 admin_error!(?e, "backend failure");
355 OperationError::Backend
356 })?;
357
358 let access = self.get_accesscontrols();
364 access.search_filter_entries(se, res).map_err(|e| {
365 admin_error!(?e, "Unable to access filter entries");
366 e
367 })
368 }
369
370 #[instrument(level = "debug", skip_all)]
371 fn exists(&mut self, ee: &ExistsEvent) -> Result<bool, OperationError> {
372 let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
373 let idxmeta = be_txn.get_idxmeta_ref();
374
375 let vfr = ee
376 .filter
377 .resolve(&ee.ident, Some(idxmeta), resolve_filter_cache)
378 .map_err(|e| {
379 admin_error!(?e, "Failed to resolve filter");
380 e
381 })?;
382
383 let lims = ee.ident.limits();
384
385 if ee.ident.is_internal() {
386 be_txn.exists(lims, &vfr).map_err(|e| {
389 admin_error!(?e, "backend failure");
390 OperationError::Backend
391 })
392 } else {
393 let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
396 admin_error!(?e, "backend failure");
397 OperationError::Backend
398 })?;
399
400 let access = self.get_accesscontrols();
407 access
408 .filter_entries(&ee.ident, &ee.filter_orig, res)
409 .map_err(|e| {
410 admin_error!(?e, "Unable to access filter entries");
411 e
412 })
413 .map(|entries| !entries.is_empty())
414 }
415 }
416
417 fn name_to_uuid(&mut self, name: &str) -> Result<Uuid, OperationError> {
418 let work = EXTRACT_VAL_DN
428 .captures(name)
429 .and_then(|caps| caps.name("val"))
430 .map(|v| v.as_str().to_lowercase())
431 .ok_or(OperationError::InvalidValueState)?;
432
433 Uuid::parse_str(&work).or_else(|_| {
435 self.get_be_txn()
436 .name2uuid(&work)?
437 .ok_or(OperationError::NoMatchingEntries)
438 })
439 }
440
441 fn sync_external_id_to_uuid(
443 &mut self,
444 external_id: &str,
445 ) -> Result<Option<Uuid>, OperationError> {
446 Uuid::parse_str(external_id).map(Some).or_else(|_| {
448 let lname = external_id.to_lowercase();
449 self.get_be_txn().externalid2uuid(lname.as_str())
450 })
451 }
452
453 fn uuid_to_spn(&mut self, uuid: Uuid) -> Result<Option<Value>, OperationError> {
454 let r = self.get_be_txn().uuid2spn(uuid)?;
455
456 if let Some(ref n) = r {
457 debug_assert!(n.is_spn() || n.is_iname());
460 }
461
462 Ok(r)
463 }
464
465 fn uuid_to_rdn(&mut self, uuid: Uuid) -> Result<String, OperationError> {
466 self.get_be_txn()
468 .uuid2rdn(uuid)
469 .map(|v| v.unwrap_or_else(|| format!("uuid={}", uuid.as_hyphenated())))
470 }
471
472 #[instrument(level = "debug", skip_all)]
474 fn internal_exists(&mut self, filter: Filter<FilterInvalid>) -> Result<bool, OperationError> {
475 let f_valid = filter
477 .validate(self.get_schema())
478 .map_err(OperationError::SchemaViolation)?;
479 let ee = ExistsEvent::new_internal(f_valid);
481 self.exists(&ee)
483 }
484
485 #[instrument(level = "debug", skip_all)]
486 fn internal_search(
487 &mut self,
488 filter: Filter<FilterInvalid>,
489 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
490 let f_valid = filter
491 .validate(self.get_schema())
492 .map_err(OperationError::SchemaViolation)?;
493 let se = SearchEvent::new_internal(f_valid);
494 self.search(&se)
495 }
496
497 #[instrument(level = "debug", skip_all)]
498 fn impersonate_search_valid(
499 &mut self,
500 f_valid: Filter<FilterValid>,
501 f_intent_valid: Filter<FilterValid>,
502 event: &Identity,
503 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
504 let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
505 self.search(&se)
506 }
507
508 fn impersonate_search_ext_valid(
510 &mut self,
511 f_valid: Filter<FilterValid>,
512 f_intent_valid: Filter<FilterValid>,
513 event: &Identity,
514 ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
515 let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
516 self.search_ext(&se)
517 }
518
519 fn impersonate_search(
521 &mut self,
522 filter: Filter<FilterInvalid>,
523 filter_intent: Filter<FilterInvalid>,
524 event: &Identity,
525 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
526 let f_valid = filter
527 .validate(self.get_schema())
528 .map_err(OperationError::SchemaViolation)?;
529 let f_intent_valid = filter_intent
530 .validate(self.get_schema())
531 .map_err(OperationError::SchemaViolation)?;
532 self.impersonate_search_valid(f_valid, f_intent_valid, event)
533 }
534
535 #[instrument(level = "debug", skip_all)]
536 fn impersonate_search_ext(
537 &mut self,
538 filter: Filter<FilterInvalid>,
539 filter_intent: Filter<FilterInvalid>,
540 event: &Identity,
541 ) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
542 let f_valid = filter
543 .validate(self.get_schema())
544 .map_err(OperationError::SchemaViolation)?;
545 let f_intent_valid = filter_intent
546 .validate(self.get_schema())
547 .map_err(OperationError::SchemaViolation)?;
548 self.impersonate_search_ext_valid(f_valid, f_intent_valid, event)
549 }
550
551 #[instrument(level = "debug", skip_all)]
554 fn internal_search_uuid(
555 &mut self,
556 uuid: Uuid,
557 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
558 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
559 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
560 error!(?e, "Filter Validate - SchemaViolation");
561 OperationError::SchemaViolation(e)
562 })?;
563 let se = SearchEvent::new_internal(f_valid);
564
565 let mut vs = self.search(&se)?;
566 match vs.pop() {
567 Some(entry) if vs.is_empty() => Ok(entry),
568 _ => Err(OperationError::NoMatchingEntries),
569 }
570 }
571
572 #[instrument(level = "debug", skip_all)]
575 fn internal_search_all_uuid(
576 &mut self,
577 uuid: Uuid,
578 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
579 let filter = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
580 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
581 error!(?e, "Filter Validate - SchemaViolation");
582 OperationError::SchemaViolation(e)
583 })?;
584 let se = SearchEvent::new_internal(f_valid);
585
586 let mut vs = self.search(&se)?;
587 match vs.pop() {
588 Some(entry) if vs.is_empty() => Ok(entry),
589 _ => Err(OperationError::NoMatchingEntries),
590 }
591 }
592
593 #[instrument(level = "debug", skip_all)]
595 fn internal_search_conflict_uuid(
596 &mut self,
597 uuid: Uuid,
598 ) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
599 let filter = filter_all!(f_and(vec![
600 f_eq(Attribute::SourceUuid, PartialValue::Uuid(uuid)),
601 f_eq(Attribute::Class, EntryClass::Conflict.into())
602 ]));
603 let f_valid = filter.validate(self.get_schema()).map_err(|e| {
604 error!(?e, "Filter Validate - SchemaViolation");
605 OperationError::SchemaViolation(e)
606 })?;
607 let se = SearchEvent::new_internal(f_valid);
608
609 self.search(&se)
610 }
611
612 #[instrument(level = "debug", skip_all)]
613 fn impersonate_search_ext_uuid(
614 &mut self,
615 uuid: Uuid,
616 event: &Identity,
617 ) -> Result<Entry<EntryReduced, EntryCommitted>, OperationError> {
618 let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
619 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
620
621 let mut vs = self.impersonate_search_ext(filter, filter_intent, event)?;
622 match vs.pop() {
623 Some(entry) if vs.is_empty() => Ok(entry),
624 _ => {
625 if vs.is_empty() {
626 Err(OperationError::NoMatchingEntries)
627 } else {
628 Err(OperationError::UniqueConstraintViolation)
630 }
631 }
632 }
633 }
634
635 #[instrument(level = "debug", skip_all)]
636 fn impersonate_search_uuid(
637 &mut self,
638 uuid: Uuid,
639 event: &Identity,
640 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
641 let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
642 let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
643
644 let mut vs = self.impersonate_search(filter, filter_intent, event)?;
645 match vs.pop() {
646 Some(entry) if vs.is_empty() => Ok(entry),
647 _ => Err(OperationError::NoMatchingEntries),
648 }
649 }
650
651 fn clone_value(&mut self, attr: &Attribute, value: &str) -> Result<Value, OperationError> {
654 let schema = self.get_schema();
655
656 match schema.get_attributes().get(attr) {
661 Some(schema_a) => {
662 match schema_a.syntax {
663 SyntaxType::Utf8String => Ok(Value::new_utf8(value.to_string())),
664 SyntaxType::Utf8StringInsensitive => Ok(Value::new_iutf8(value)),
665 SyntaxType::Utf8StringIname => Ok(Value::new_iname(value)),
666 SyntaxType::Boolean => Value::new_bools(value)
667 .ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
668 SyntaxType::SyntaxId => Value::new_syntaxs(value)
669 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
670 SyntaxType::IndexId => Value::new_indexes(value)
671 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
672 SyntaxType::CredentialType => CredentialType::try_from(value)
673 .map(Value::CredentialType)
674 .map_err(|()| OperationError::InvalidAttribute("Invalid CredentialType syntax".to_string())),
675 SyntaxType::Uuid => {
676 let un = self
679 .name_to_uuid(value)
680 .unwrap_or(UUID_DOES_NOT_EXIST);
681 Ok(Value::Uuid(un))
682 }
683 SyntaxType::ReferenceUuid => {
684 let un = self
685 .name_to_uuid(value)
686 .unwrap_or(UUID_DOES_NOT_EXIST);
687 Ok(Value::Refer(un))
688 }
689 SyntaxType::JsonFilter => Value::new_json_filter_s(value)
690 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
691 SyntaxType::Image => Value::new_image(value),
692
693 SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
694 SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
695 SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
696 SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
697 SyntaxType::Uint32 => Value::new_uint32_str(value)
698 .ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
699 SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
700 SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
701 .ok_or_else(|| OperationError::InvalidAttribute("Invalid NsUniqueId syntax".to_string())),
702 SyntaxType::DateTime => Value::new_datetime_s(value)
703 .ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
704 SyntaxType::EmailAddress => Value::new_email_address_s(value)
705 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Email Address syntax".to_string())),
706 SyntaxType::Url => Value::new_url_s(value)
707 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Url (whatwg/url) syntax".to_string())),
708 SyntaxType::OauthScope => Value::new_oauthscope(value)
709 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Oauth Scope syntax".to_string())),
710 SyntaxType::WebauthnAttestationCaList => Value::new_webauthn_attestation_ca_list(value)
711 .ok_or_else(|| OperationError::InvalidAttribute("Invalid Webauthn Attestation CA List".to_string())),
712 SyntaxType::OauthScopeMap => Err(OperationError::InvalidAttribute("Oauth Scope Maps can not be supplied through modification - please use the IDM api".to_string())),
713 SyntaxType::OauthClaimMap => Err(OperationError::InvalidAttribute("Oauth Claim Maps can not be supplied through modification - please use the IDM api".to_string())),
714 SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute("Private Binary Values can not be supplied through modification".to_string())),
715 SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
716 SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
717 SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute("AttestedPasskey Values can not be supplied through modification".to_string())),
718 SyntaxType::Session => Err(OperationError::InvalidAttribute("Session Values can not be supplied through modification".to_string())),
719 SyntaxType::ApiToken => Err(OperationError::InvalidAttribute("ApiToken Values can not be supplied through modification".to_string())),
720 SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())),
721 SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())),
722 SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute("Oauth2Session Values can not be supplied through modification".to_string())),
723 SyntaxType::UiHint => UiHint::from_str(value)
724 .map(Value::UiHint)
725 .map_err(|()| OperationError::InvalidAttribute("Invalid uihint syntax".to_string())),
726 SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute("TotpSecret Values can not be supplied through modification".to_string())),
727 SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute("Audit logs are generated and not able to be set.".to_string())),
728 SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute("Ec keys are generated and not able to be set.".to_string())),
729 SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute("Internal keys are generated and not able to be set.".to_string())),
730 SyntaxType::HexString => Value::new_hex_string_s(value)
731 .ok_or_else(|| OperationError::InvalidAttribute("Invalid hex string syntax".to_string())),
732 SyntaxType::Certificate => Value::new_certificate_s(value)
733 .ok_or_else(|| OperationError::InvalidAttribute("Invalid x509 certificate syntax".to_string())),
734 SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute("ApplicationPassword values can not be supplied through modification".to_string())),
735 }
736 }
737 None => {
738 Err(OperationError::InvalidAttributeName(attr.to_string()))
741 }
742 }
743 }
744
745 fn clone_partialvalue(
746 &mut self,
747 attr: &Attribute,
748 value: &str,
749 ) -> Result<PartialValue, OperationError> {
750 let schema = self.get_schema();
751
752 match schema.get_attributes().get(attr) {
754 Some(schema_a) => {
755 match schema_a.syntax {
756 SyntaxType::Utf8String | SyntaxType::TotpSecret => {
757 Ok(PartialValue::new_utf8(value.to_string()))
758 }
759 SyntaxType::Utf8StringInsensitive
760 | SyntaxType::JwsKeyEs256
761 | SyntaxType::JwsKeyRs256 => Ok(PartialValue::new_iutf8(value)),
762 SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
763 SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
764 OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
765 }),
766 SyntaxType::SyntaxId => PartialValue::new_syntaxs(value).ok_or_else(|| {
767 OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
768 }),
769 SyntaxType::IndexId => PartialValue::new_indexes(value).ok_or_else(|| {
770 OperationError::InvalidAttribute("Invalid Index syntax".to_string())
771 }),
772 SyntaxType::CredentialType => CredentialType::try_from(value)
773 .map(PartialValue::CredentialType)
774 .map_err(|()| {
775 OperationError::InvalidAttribute(
776 "Invalid credentialtype syntax".to_string(),
777 )
778 }),
779 SyntaxType::Uuid => {
780 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
781 Ok(PartialValue::Uuid(un))
782 }
783 SyntaxType::ReferenceUuid
787 | SyntaxType::OauthScopeMap
788 | SyntaxType::Session
789 | SyntaxType::ApiToken
790 | SyntaxType::Oauth2Session
791 | SyntaxType::ApplicationPassword => {
792 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
793 Ok(PartialValue::Refer(un))
794 }
795 SyntaxType::OauthClaimMap => self
796 .name_to_uuid(value)
797 .map(PartialValue::Refer)
798 .or_else(|_| Ok(PartialValue::new_iutf8(value))),
799
800 SyntaxType::JsonFilter => {
801 PartialValue::new_json_filter_s(value).ok_or_else(|| {
802 OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
803 })
804 }
805 SyntaxType::Credential => Ok(PartialValue::new_credential_tag(value)),
806 SyntaxType::SecretUtf8String => Ok(PartialValue::new_secret_str()),
807 SyntaxType::SshKey => Ok(PartialValue::new_sshkey_tag_s(value)),
808 SyntaxType::SecurityPrincipalName => {
809 PartialValue::new_spn_s(value).ok_or_else(|| {
810 OperationError::InvalidAttribute("Invalid spn syntax".to_string())
811 })
812 }
813 SyntaxType::Uint32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
814 OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
815 }),
816 SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
817 OperationError::InvalidAttribute("Invalid cid syntax".to_string())
818 }),
819 SyntaxType::NsUniqueId => Ok(PartialValue::new_nsuniqueid_s(value)),
820 SyntaxType::DateTime => PartialValue::new_datetime_s(value).ok_or_else(|| {
821 OperationError::InvalidAttribute(
822 "Invalid DateTime (rfc3339) syntax".to_string(),
823 )
824 }),
825 SyntaxType::EmailAddress => Ok(PartialValue::new_email_address_s(value)),
826 SyntaxType::Url => PartialValue::new_url_s(value).ok_or_else(|| {
827 OperationError::InvalidAttribute(
828 "Invalid Url (whatwg/url) syntax".to_string(),
829 )
830 }),
831 SyntaxType::OauthScope => Ok(PartialValue::new_oauthscope(value)),
832 SyntaxType::PrivateBinary => Ok(PartialValue::PrivateBinary),
833 SyntaxType::IntentToken => PartialValue::new_intenttoken_s(value.to_string())
834 .ok_or_else(|| {
835 OperationError::InvalidAttribute(
836 "Invalid Intent Token ID (uuid) syntax".to_string(),
837 )
838 }),
839 SyntaxType::Passkey => PartialValue::new_passkey_s(value).ok_or_else(|| {
840 OperationError::InvalidAttribute("Invalid Passkey UUID syntax".to_string())
841 }),
842 SyntaxType::AttestedPasskey => PartialValue::new_attested_passkey_s(value)
843 .ok_or_else(|| {
844 OperationError::InvalidAttribute(
845 "Invalid AttestedPasskey UUID syntax".to_string(),
846 )
847 }),
848 SyntaxType::UiHint => UiHint::from_str(value)
849 .map(PartialValue::UiHint)
850 .map_err(|()| {
851 OperationError::InvalidAttribute("Invalid uihint syntax".to_string())
852 }),
853 SyntaxType::AuditLogString => Ok(PartialValue::new_utf8s(value)),
854 SyntaxType::EcKeyPrivate => Ok(PartialValue::SecretValue),
855 SyntaxType::Image => Ok(PartialValue::new_utf8s(value)),
856 SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
857 "Invalid - unable to query attestation CA list".to_string(),
858 )),
859 SyntaxType::HexString | SyntaxType::KeyInternal | SyntaxType::Certificate => {
860 PartialValue::new_hex_string_s(value).ok_or_else(|| {
861 OperationError::InvalidAttribute(
862 "Invalid syntax, expected hex string".to_string(),
863 )
864 })
865 }
866 }
867 }
868 None => {
869 Err(OperationError::InvalidAttributeName(attr.to_string()))
872 }
873 }
874 }
875
876 fn resolve_scim_interim(
877 &mut self,
878 scim_value_intermediate: ScimValueIntermediate,
879 ) -> Result<Option<ScimValueKanidm>, OperationError> {
880 match scim_value_intermediate {
881 ScimValueIntermediate::References(uuids) => {
882 let scim_references = uuids
883 .into_iter()
884 .map(|uuid| {
885 self.uuid_to_spn(uuid)
886 .and_then(|maybe_value| {
887 maybe_value.ok_or(OperationError::InvalidValueState)
888 })
889 .map(|value| ScimReference {
890 uuid,
891 value: value.to_proto_string_clone(),
892 })
893 })
894 .collect::<Result<Vec<_>, _>>()?;
895 Ok(Some(ScimValueKanidm::EntryReferences(scim_references)))
896 }
897 ScimValueIntermediate::Oauth2ClaimMap(unresolved_maps) => {
898 let scim_claim_maps = unresolved_maps
899 .into_iter()
900 .map(
901 |UnresolvedScimValueOauth2ClaimMap {
902 group_uuid,
903 claim,
904 join_char,
905 values,
906 }| {
907 self.uuid_to_spn(group_uuid)
908 .and_then(|maybe_value| {
909 maybe_value.ok_or(OperationError::InvalidValueState)
910 })
911 .map(|value| ScimOAuth2ClaimMap {
912 group: value.to_proto_string_clone(),
913 group_uuid,
914 claim,
915 join_char,
916 values,
917 })
918 },
919 )
920 .collect::<Result<Vec<_>, _>>()?;
921
922 Ok(Some(ScimValueKanidm::OAuth2ClaimMap(scim_claim_maps)))
923 }
924
925 ScimValueIntermediate::Oauth2ScopeMap(unresolved_maps) => {
926 let scim_claim_maps = unresolved_maps
927 .into_iter()
928 .map(|UnresolvedScimValueOauth2ScopeMap { group_uuid, scopes }| {
929 self.uuid_to_spn(group_uuid)
930 .and_then(|maybe_value| {
931 maybe_value.ok_or(OperationError::InvalidValueState)
932 })
933 .map(|value| ScimOAuth2ScopeMap {
934 group: value.to_proto_string_clone(),
935 group_uuid,
936 scopes,
937 })
938 })
939 .collect::<Result<Vec<_>, _>>()?;
940
941 Ok(Some(ScimValueKanidm::OAuth2ScopeMap(scim_claim_maps)))
942 }
943 }
944 }
945
946 fn resolve_scim_json_get(
947 &mut self,
948 attr: &Attribute,
949 value: &JsonValue,
950 ) -> Result<PartialValue, OperationError> {
951 let schema = self.get_schema();
952 let Some(schema_a) = schema.get_attributes().get(attr) else {
954 return Err(OperationError::InvalidAttributeName(attr.to_string()));
957 };
958
959 debug!(schema_syntax = ?schema_a.syntax, ?value);
960
961 match schema_a.syntax {
962 SyntaxType::Utf8String => {
963 let JsonValue::String(value) = value else {
964 return Err(OperationError::InvalidAttribute(attr.to_string()));
965 };
966 Ok(PartialValue::Utf8(value.to_string()))
967 }
968 SyntaxType::Utf8StringInsensitive => {
969 let JsonValue::String(value) = value else {
970 return Err(OperationError::InvalidAttribute(attr.to_string()));
971 };
972 Ok(PartialValue::new_iutf8(value))
973 }
974 SyntaxType::Utf8StringIname => {
975 let JsonValue::String(value) = value else {
976 return Err(OperationError::InvalidAttribute(attr.to_string()));
977 };
978 Ok(PartialValue::new_iname(value))
979 }
980 SyntaxType::Uuid => {
981 let JsonValue::String(value) = value else {
982 return Err(OperationError::InvalidAttribute(attr.to_string()));
983 };
984
985 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
986 Ok(PartialValue::Uuid(un))
987 }
988 SyntaxType::Boolean => {
989 let JsonValue::Bool(value) = value else {
990 return Err(OperationError::InvalidAttribute(attr.to_string()));
991 };
992 Ok(PartialValue::Bool(*value))
993 }
994 SyntaxType::SyntaxId => {
995 let JsonValue::String(value) = value else {
996 return Err(OperationError::InvalidAttribute(attr.to_string()));
997 };
998 let Ok(value) = SyntaxType::try_from(value.as_str()) else {
999 return Err(OperationError::InvalidAttribute(attr.to_string()));
1000 };
1001 Ok(PartialValue::Syntax(value))
1002 }
1003 SyntaxType::ReferenceUuid
1004 | SyntaxType::OauthScopeMap
1005 | SyntaxType::Session
1006 | SyntaxType::ApiToken
1007 | SyntaxType::Oauth2Session
1008 | SyntaxType::ApplicationPassword => {
1009 let JsonValue::String(value) = value else {
1010 return Err(OperationError::InvalidAttribute(attr.to_string()));
1011 };
1012
1013 let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
1014 Ok(PartialValue::Refer(un))
1015 }
1016
1017 _ => Err(OperationError::InvalidAttribute(attr.to_string())),
1018 }
1019 }
1020
1021 fn resolve_valueset_intermediate(
1022 &mut self,
1023 vs_inter: ValueSetIntermediate,
1024 ) -> Result<ValueSet, OperationError> {
1025 match vs_inter {
1026 ValueSetIntermediate::References {
1027 mut resolved,
1028 unresolved,
1029 } => {
1030 for value in unresolved {
1031 let un = self.name_to_uuid(value.as_str()).unwrap_or_else(|_| {
1032 warn!(
1033 ?value,
1034 "Value can not be resolved to a uuid - assuming it does not exist."
1035 );
1036 UUID_DOES_NOT_EXIST
1037 });
1038
1039 resolved.insert(un);
1040 }
1041
1042 let vs = ValueSetRefer::from_set(resolved);
1043 Ok(vs)
1044 }
1045
1046 ValueSetIntermediate::Oauth2ClaimMap {
1047 mut resolved,
1048 unresolved,
1049 } => {
1050 resolved.extend(unresolved.into_iter().map(
1051 |UnresolvedValueSetOauth2ClaimMap {
1052 group_name,
1053 claim,
1054 join_char,
1055 claim_values,
1056 }| {
1057 let group_uuid =
1058 self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1059 warn!(
1060 ?group_name,
1061 "Value can not be resolved to a uuid - assuming it does not exist."
1062 );
1063 UUID_DOES_NOT_EXIST
1064 });
1065
1066 ResolvedValueSetOauth2ClaimMap {
1067 group_uuid,
1068 claim,
1069 join_char,
1070 claim_values,
1071 }
1072 },
1073 ));
1074
1075 let vs = ValueSetOauthClaimMap::from_set(resolved);
1076 Ok(vs)
1077 }
1078
1079 ValueSetIntermediate::Oauth2ScopeMap {
1080 mut resolved,
1081 unresolved,
1082 } => {
1083 resolved.extend(unresolved.into_iter().map(
1084 |UnresolvedValueSetOauth2ScopeMap { group_name, scopes }| {
1085 let group_uuid =
1086 self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
1087 warn!(
1088 ?group_name,
1089 "Value can not be resolved to a uuid - assuming it does not exist."
1090 );
1091 UUID_DOES_NOT_EXIST
1092 });
1093
1094 ResolvedValueSetOauth2ScopeMap { group_uuid, scopes }
1095 },
1096 ));
1097
1098 let vs = ValueSetOauthScopeMap::from_set(resolved);
1099 Ok(vs)
1100 }
1101 }
1102 }
1103
1104 fn resolve_valueset(&mut self, value: &ValueSet) -> Result<Vec<String>, OperationError> {
1106 if let Some(r_set) = value.as_refer_set() {
1107 let v: Result<Vec<_>, _> = r_set
1108 .iter()
1109 .copied()
1110 .map(|ur| {
1111 let nv = self.uuid_to_spn(ur)?;
1112 match nv {
1113 Some(v) => Ok(v.to_proto_string_clone()),
1114 None => Ok(uuid_to_proto_string(ur)),
1115 }
1116 })
1117 .collect();
1118 v
1119 } else if let Some(r_map) = value.as_oauthscopemap() {
1120 let v: Result<Vec<_>, _> = r_map
1121 .iter()
1122 .map(|(u, m)| {
1123 let nv = self.uuid_to_spn(*u)?;
1124 let u = match nv {
1125 Some(v) => v.to_proto_string_clone(),
1126 None => uuid_to_proto_string(*u),
1127 };
1128 Ok(format!("{u}: {m:?}"))
1129 })
1130 .collect();
1131 v
1132 } else if let Some(r_map) = value.as_oauthclaim_map() {
1133 let mut v = Vec::with_capacity(0);
1134 for (claim_name, mapping) in r_map.iter() {
1135 for (group_ref, claims) in mapping.values() {
1136 let join_char = mapping.join().to_str();
1137
1138 let nv = self.uuid_to_spn(*group_ref)?;
1139 let resolved_id = match nv {
1140 Some(v) => v.to_proto_string_clone(),
1141 None => uuid_to_proto_string(*group_ref),
1142 };
1143
1144 let joined = str_concat!(claims, ",");
1145
1146 v.push(format!("{claim_name}:{resolved_id}:{join_char}:{joined:?}"))
1147 }
1148 }
1149 Ok(v)
1150 } else {
1151 let v: Vec<_> = value.to_proto_string_clone_iter().collect();
1152 Ok(v)
1153 }
1154 }
1155
1156 fn resolve_valueset_ldap(
1157 &mut self,
1158 value: &ValueSet,
1159 basedn: &str,
1160 ) -> Result<Vec<Vec<u8>>, OperationError> {
1161 if let Some(r_set) = value.as_refer_set() {
1162 let v: Result<Vec<_>, _> = r_set
1163 .iter()
1164 .copied()
1165 .map(|ur| {
1166 let rdn = self.uuid_to_rdn(ur)?;
1167 Ok(format!("{rdn},{basedn}").into_bytes())
1168 })
1169 .collect();
1170 v
1171 } else if let Some(key_iter) = value.as_sshpubkey_string_iter() {
1174 let v: Vec<_> = key_iter.map(|s| s.into_bytes()).collect();
1175 Ok(v)
1176 } else {
1177 let v: Vec<_> = value
1178 .to_proto_string_clone_iter()
1179 .map(|s| s.into_bytes())
1180 .collect();
1181 Ok(v)
1182 }
1183 }
1184
1185 fn get_db_domain(&mut self) -> Result<Arc<EntrySealedCommitted>, OperationError> {
1186 self.internal_search_uuid(UUID_DOMAIN_INFO)
1187 }
1188
1189 fn get_domain_key_object_handle(&self) -> Result<Arc<KeyObject>, OperationError> {
1190 self.get_key_providers()
1191 .get_key_object_handle(UUID_DOMAIN_INFO)
1192 .ok_or(OperationError::KP0031KeyObjectNotFound)
1193 }
1194
1195 fn get_domain_es256_private_key(&mut self) -> Result<Vec<u8>, OperationError> {
1196 self.internal_search_uuid(UUID_DOMAIN_INFO)
1197 .and_then(|e| {
1198 e.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
1199 .map(|s| s.to_vec())
1200 .ok_or(OperationError::InvalidEntryState)
1201 })
1202 .map_err(|e| {
1203 admin_error!(?e, "Error getting domain es256 key");
1204 e
1205 })
1206 }
1207
1208 fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
1209 self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| {
1210 entry
1211 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
1212 .unwrap_or(true)
1213 })
1214 }
1215
1216 fn get_sc_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
1219 self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1220 .map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) {
1221 Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1222 None => HashSet::default(),
1223 })
1224 .map_err(|e| {
1225 error!(
1226 ?e,
1227 "Failed to retrieve password badlist from system configuration"
1228 );
1229 e
1230 })
1231 }
1232
1233 fn get_sc_denied_names(&mut self) -> Result<HashSet<String>, OperationError> {
1236 self.internal_search_uuid(UUID_SYSTEM_CONFIG)
1237 .map(|e| match e.get_ava_iter_iname(Attribute::DeniedName) {
1238 Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
1239 None => HashSet::default(),
1240 })
1241 .map_err(|e| {
1242 error!(
1243 ?e,
1244 "Failed to retrieve denied names from system configuration"
1245 );
1246 e
1247 })
1248 }
1249
1250 fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1251 self.internal_search(filter!(f_eq(
1252 Attribute::Class,
1253 EntryClass::OAuth2ResourceServer.into(),
1254 )))
1255 }
1256
1257 fn get_applications_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
1258 self.internal_search(filter!(f_eq(
1259 Attribute::Class,
1260 EntryClass::Application.into(),
1261 )))
1262 }
1263
1264 #[instrument(level = "debug", skip_all)]
1265 fn consumer_get_state(&mut self) -> Result<ReplRuvRange, OperationError> {
1266 let domain_uuid = self.get_domain_uuid();
1291
1292 let ruv_snapshot = self.get_be_txn().get_ruv();
1295
1296 ruv_snapshot
1298 .current_ruv_range()
1299 .map(|ranges| ReplRuvRange::V1 {
1300 domain_uuid,
1301 ranges,
1302 })
1303 }
1304}
1305
1306impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
1310 type AccessControlsTransactionType = AccessControlsReadTransaction<'a>;
1311 type BackendTransactionType = BackendReadTransaction<'a>;
1312 type SchemaTransactionType = SchemaReadTransaction;
1313 type KeyProvidersTransactionType = KeyProvidersReadTransaction;
1314
1315 fn get_be_txn(&mut self) -> &mut BackendReadTransaction<'a> {
1316 &mut self.be_txn
1317 }
1318
1319 fn get_schema<'b>(&self) -> &'b SchemaReadTransaction {
1320 unsafe {
1324 let s = (&self.schema) as *const _;
1325 &*s
1326 }
1327 }
1328
1329 fn get_accesscontrols(&self) -> &AccessControlsReadTransaction<'a> {
1330 &self.accesscontrols
1331 }
1332
1333 fn get_key_providers(&self) -> &KeyProvidersReadTransaction {
1334 &self.key_providers
1335 }
1336
1337 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1338 Some(&mut self.resolve_filter_cache)
1339 }
1340
1341 fn get_resolve_filter_cache_and_be_txn(
1342 &mut self,
1343 ) -> (
1344 &mut BackendReadTransaction<'a>,
1345 Option<&mut ResolveFilterCacheReadTxn<'a>>,
1346 ) {
1347 (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1348 }
1349
1350 fn pw_badlist(&self) -> &HashSet<String> {
1351 &self.system_config.pw_badlist
1352 }
1353
1354 fn denied_names(&self) -> &HashSet<String> {
1355 &self.system_config.denied_names
1356 }
1357
1358 fn get_domain_version(&self) -> DomainVersion {
1359 self.d_info.d_vers
1360 }
1361
1362 fn get_domain_patch_level(&self) -> u32 {
1363 self.d_info.d_patch_level
1364 }
1365
1366 fn get_domain_development_taint(&self) -> bool {
1367 self.d_info.d_devel_taint
1368 }
1369
1370 fn get_domain_uuid(&self) -> Uuid {
1371 self.d_info.d_uuid
1372 }
1373
1374 fn get_domain_name(&self) -> &str {
1375 &self.d_info.d_name
1376 }
1377
1378 fn get_domain_display_name(&self) -> &str {
1379 &self.d_info.d_display
1380 }
1381
1382 fn get_domain_image_value(&self) -> Option<ImageValue> {
1383 self.d_info.d_image.clone()
1384 }
1385}
1386
1387impl QueryServerReadTransaction<'_> {
1388 pub(crate) fn trim_cid(&self) -> &Cid {
1389 &self.trim_cid
1390 }
1391
1392 pub fn domain_info(&mut self) -> Result<ProtoDomainInfo, OperationError> {
1394 let d_info = &self.d_info;
1395
1396 Ok(ProtoDomainInfo {
1397 name: d_info.d_name.clone(),
1398 displayname: d_info.d_display.clone(),
1399 uuid: d_info.d_uuid,
1400 level: d_info.d_vers,
1401 })
1402 }
1403
1404 pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
1408 let be_errs = self.get_be_txn().verify();
1412
1413 if !be_errs.is_empty() {
1414 return be_errs;
1415 }
1416
1417 let sc_errs = self.get_schema().validate();
1419
1420 if !sc_errs.is_empty() {
1421 return sc_errs;
1422 }
1423
1424 let idx_errs = self.get_be_txn().verify_indexes();
1428
1429 if !idx_errs.is_empty() {
1430 return idx_errs;
1431 }
1432
1433 let mut results = Vec::with_capacity(0);
1436
1437 let schema = self.get_schema();
1440
1441 let filt_all = filter!(f_pres(Attribute::Class));
1442 let all_entries = match self.internal_search(filt_all) {
1443 Ok(a) => a,
1444 Err(_e) => return vec![Err(ConsistencyError::QueryServerSearchFailure)],
1445 };
1446
1447 for e in all_entries {
1448 e.verify(schema, &mut results)
1449 }
1450
1451 self.get_be_txn().verify_ruv(&mut results);
1453
1454 Plugins::run_verify(self, &mut results);
1460 results
1463 }
1464
1465 #[instrument(level = "debug", skip_all)]
1466 pub fn scim_entry_id_get_ext(
1467 &mut self,
1468 uuid: Uuid,
1469 class: EntryClass,
1470 query: ScimEntryGetQuery,
1471 ident: Identity,
1472 ) -> Result<ScimEntryKanidm, OperationError> {
1473 let filter_intent = filter!(f_and!([
1474 f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)),
1475 f_eq(Attribute::Class, class.into())
1476 ]));
1477
1478 let f_intent_valid = filter_intent
1479 .validate(self.get_schema())
1480 .map_err(OperationError::SchemaViolation)?;
1481
1482 let f_valid = f_intent_valid.clone().into_ignore_hidden();
1483
1484 let r_attrs = query
1485 .attributes
1486 .map(|attr_set| attr_set.into_iter().collect());
1487
1488 let se = SearchEvent {
1489 ident,
1490 filter: f_valid,
1491 filter_orig: f_intent_valid,
1492 attrs: r_attrs,
1493 effective_access_check: query.ext_access_check,
1494 };
1495
1496 let mut vs = self.search_ext(&se)?;
1497 match vs.pop() {
1498 Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
1499 _ => {
1500 if vs.is_empty() {
1501 Err(OperationError::NoMatchingEntries)
1502 } else {
1503 Err(OperationError::UniqueConstraintViolation)
1505 }
1506 }
1507 }
1508 }
1509
1510 #[instrument(level = "debug", skip_all)]
1511 pub fn scim_search_ext(
1512 &mut self,
1513 ident: Identity,
1514 filter: ScimFilter,
1515 query: ScimEntryGetQuery,
1516 ) -> Result<ScimListResponse, OperationError> {
1517 let filter = if let Some(user_filter) = query.filter {
1520 ScimFilter::And(Box::new(filter), Box::new(user_filter.clone()))
1521 } else {
1522 filter
1523 };
1524
1525 let filter_intent = Filter::from_scim_ro(&ident, &filter, self)?;
1526
1527 let f_intent_valid = filter_intent
1528 .validate(self.get_schema())
1529 .map_err(OperationError::SchemaViolation)?;
1530
1531 let f_valid = f_intent_valid.clone().into_ignore_hidden();
1532
1533 let r_attrs = query
1534 .attributes
1535 .map(|attr_set| attr_set.into_iter().collect());
1536
1537 let se = SearchEvent {
1538 ident,
1539 filter: f_valid,
1540 filter_orig: f_intent_valid,
1541 attrs: r_attrs,
1542 effective_access_check: query.ext_access_check,
1543 };
1544
1545 let mut result_set = self.search_ext(&se)?;
1546
1547 let total_results = result_set.len() as u64;
1549
1550 if let Some(sort_attr) = query.sort_by {
1556 result_set.sort_unstable_by(|entry_left, entry_right| {
1557 let left = entry_left.get_ava_set(&sort_attr);
1558 let right = entry_right.get_ava_set(&sort_attr);
1559 match (left, right) {
1560 (Some(left), Some(right)) => left.cmp(right),
1561 (Some(_), None) => std::cmp::Ordering::Less,
1562 (None, Some(_)) => std::cmp::Ordering::Greater,
1563 (None, None) => std::cmp::Ordering::Equal,
1564 }
1565 });
1566 }
1567
1568 let (items_per_page, start_index, paginated_result_set) = if let Some(count) = query.count {
1570 let count: u64 = count.get();
1571 let start_index: u64 = query
1574 .start_index
1575 .map(|non_zero_index|
1576 non_zero_index.get() - 1)
1578 .unwrap_or_default();
1579
1580 if start_index as usize > result_set.len() {
1582 return Err(OperationError::SC0029PaginationOutOfBounds);
1585 }
1586
1587 let mut result_set = result_set.split_off(start_index as usize);
1588 result_set.truncate(count as usize);
1589
1590 (
1591 NonZeroU64::new(count),
1592 NonZeroU64::new(start_index + 1),
1593 result_set,
1594 )
1595 } else {
1596 (None, None, result_set)
1598 };
1599
1600 let resources = paginated_result_set
1601 .into_iter()
1602 .map(|entry| entry.to_scim_kanidm(self))
1603 .collect::<Result<Vec<_>, _>>()?;
1604
1605 Ok(ScimListResponse {
1606 schemas: Vec::with_capacity(0),
1608 total_results,
1609 items_per_page,
1610 start_index,
1611 resources,
1612 })
1613 }
1614}
1615
1616impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
1617 type AccessControlsTransactionType = AccessControlsWriteTransaction<'a>;
1618 type BackendTransactionType = BackendWriteTransaction<'a>;
1619 type SchemaTransactionType = SchemaWriteTransaction<'a>;
1620 type KeyProvidersTransactionType = KeyProvidersWriteTransaction<'a>;
1621
1622 fn get_be_txn(&mut self) -> &mut BackendWriteTransaction<'a> {
1623 &mut self.be_txn
1624 }
1625
1626 fn get_schema<'b>(&self) -> &'b SchemaWriteTransaction<'a> {
1627 unsafe {
1631 let s = (&self.schema) as *const _;
1632 &*s
1633 }
1634 }
1635
1636 fn get_accesscontrols(&self) -> &AccessControlsWriteTransaction<'a> {
1637 &self.accesscontrols
1638 }
1639
1640 fn get_key_providers(&self) -> &KeyProvidersWriteTransaction<'a> {
1641 &self.key_providers
1642 }
1643
1644 fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
1645 if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1646 None
1647 } else {
1648 Some(&mut self.resolve_filter_cache)
1649 }
1650 }
1651
1652 fn get_resolve_filter_cache_and_be_txn(
1653 &mut self,
1654 ) -> (
1655 &mut BackendWriteTransaction<'a>,
1656 Option<&mut ResolveFilterCacheReadTxn<'a>>,
1657 ) {
1658 if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
1659 (&mut self.be_txn, None)
1660 } else {
1661 (&mut self.be_txn, Some(&mut self.resolve_filter_cache))
1662 }
1663 }
1664
1665 fn pw_badlist(&self) -> &HashSet<String> {
1666 &self.system_config.pw_badlist
1667 }
1668
1669 fn denied_names(&self) -> &HashSet<String> {
1670 &self.system_config.denied_names
1671 }
1672
1673 fn get_domain_version(&self) -> DomainVersion {
1674 self.d_info.d_vers
1675 }
1676
1677 fn get_domain_patch_level(&self) -> u32 {
1678 self.d_info.d_patch_level
1679 }
1680
1681 fn get_domain_development_taint(&self) -> bool {
1682 self.d_info.d_devel_taint
1683 }
1684
1685 fn get_domain_uuid(&self) -> Uuid {
1686 self.d_info.d_uuid
1687 }
1688
1689 fn get_domain_name(&self) -> &str {
1691 &self.d_info.d_name
1692 }
1693
1694 fn get_domain_display_name(&self) -> &str {
1695 &self.d_info.d_display
1696 }
1697
1698 fn get_domain_image_value(&self) -> Option<ImageValue> {
1699 self.d_info.d_image.clone()
1700 }
1701}
1702
1703impl QueryServer {
1704 pub fn new(
1705 be: Backend,
1706 schema: Schema,
1707 domain_name: String,
1708 curtime: Duration,
1709 ) -> Result<Self, OperationError> {
1710 let (s_uuid, d_uuid, ts_max) = {
1711 let mut wr = be.write()?;
1712 let s_uuid = wr.get_db_s_uuid()?;
1713 let d_uuid = wr.get_db_d_uuid()?;
1714 let ts_max = wr.get_db_ts_max(curtime)?;
1715 wr.commit()?;
1716 (s_uuid, d_uuid, ts_max)
1717 };
1718
1719 let pool_size = be.get_pool_size();
1720
1721 debug!("Server UUID -> {:?}", s_uuid);
1722 debug!("Domain UUID -> {:?}", d_uuid);
1723 debug!("Domain Name -> {:?}", domain_name);
1724
1725 let d_info = Arc::new(CowCell::new(DomainInfo {
1726 d_uuid,
1727 d_vers: DOMAIN_LEVEL_0,
1730 d_patch_level: 0,
1731 d_name: domain_name.clone(),
1732 d_display: domain_name,
1735 d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(),
1737 d_ldap_allow_unix_pw_bind: false,
1738 d_allow_easter_eggs: false,
1739 d_image: None,
1740 }));
1741
1742 let cid = Cid::new_lamport(s_uuid, curtime, &ts_max);
1743 let cid_max = Arc::new(CowCell::new(cid));
1744
1745 let system_config = Arc::new(CowCell::new(SystemConfig::default()));
1747
1748 let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
1749
1750 let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
1751
1752 let resolve_filter_cache = Arc::new(
1753 ARCacheBuilder::new()
1754 .set_size(RESOLVE_FILTER_CACHE_MAX, RESOLVE_FILTER_CACHE_LOCAL)
1755 .set_reader_quiesce(true)
1756 .build()
1757 .ok_or_else(|| {
1758 error!("Failed to build filter resolve cache");
1759 OperationError::DB0003FilterResolveCacheBuild
1760 })?,
1761 );
1762
1763 let key_providers = Arc::new(KeyProviders::default());
1764
1765 debug_assert!(pool_size > 0);
1768 let read_ticket_pool = std::cmp::max(pool_size - 1, 1);
1769
1770 Ok(QueryServer {
1771 phase,
1772 d_info,
1773 system_config,
1774 be,
1775 schema: Arc::new(schema),
1776 accesscontrols: Arc::new(AccessControls::default()),
1777 db_tickets: Arc::new(Semaphore::new(pool_size as usize)),
1778 read_tickets: Arc::new(Semaphore::new(read_ticket_pool as usize)),
1779 write_ticket: Arc::new(Semaphore::new(1)),
1780 resolve_filter_cache,
1781 dyngroup_cache,
1782 cid_max,
1783 key_providers,
1784 })
1785 }
1786
1787 pub fn try_quiesce(&self) {
1788 self.be.try_quiesce();
1789 self.accesscontrols.try_quiesce();
1790 self.resolve_filter_cache.try_quiesce();
1791 }
1792
1793 #[instrument(level = "debug", skip_all)]
1794 async fn read_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
1795 let read_ticket = if cfg!(test) {
1799 self.read_tickets
1800 .try_acquire()
1801 .inspect_err(|err| {
1802 error!(?err, "Unable to acquire read ticket!");
1803 })
1804 .ok()?
1805 } else {
1806 let fut = tokio::time::timeout(
1807 Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
1808 self.read_tickets.acquire(),
1809 );
1810
1811 match fut.await {
1812 Ok(Ok(ticket)) => ticket,
1813 Ok(Err(_)) => {
1814 error!("Failed to acquire read ticket, may be poisoned.");
1815 return None;
1816 }
1817 Err(_) => {
1818 error!("Failed to acquire read ticket, server is overloaded.");
1819 return None;
1820 }
1821 }
1822 };
1823
1824 let db_ticket = if cfg!(test) {
1829 self.db_tickets
1830 .try_acquire()
1831 .inspect_err(|err| {
1832 error!(?err, "Unable to acquire database ticket!");
1833 })
1834 .ok()?
1835 } else {
1836 self.db_tickets
1837 .acquire()
1838 .await
1839 .inspect_err(|err| {
1840 error!(?err, "Unable to acquire database ticket!");
1841 })
1842 .ok()?
1843 };
1844
1845 Some((read_ticket, db_ticket))
1846 }
1847
1848 pub async fn read(&self) -> Result<QueryServerReadTransaction<'_>, OperationError> {
1849 let (read_ticket, db_ticket) = self
1850 .read_acquire_ticket()
1851 .await
1852 .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
1853 let schema = self.schema.read();
1857
1858 let cid_max = self.cid_max.read();
1859 let trim_cid = cid_max.sub_secs(CHANGELOG_MAX_AGE)?;
1860
1861 let be_txn = self.be.read()?;
1862
1863 Ok(QueryServerReadTransaction {
1864 be_txn,
1865 schema,
1866 d_info: self.d_info.read(),
1867 system_config: self.system_config.read(),
1868 accesscontrols: self.accesscontrols.read(),
1869 key_providers: self.key_providers.read(),
1870 _db_ticket: db_ticket,
1871 _read_ticket: read_ticket,
1872 resolve_filter_cache: self.resolve_filter_cache.read(),
1873 trim_cid,
1874 })
1875 }
1876
1877 #[instrument(level = "debug", skip_all)]
1878 async fn write_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
1879 let write_ticket = if cfg!(test) {
1881 self.write_ticket
1882 .try_acquire()
1883 .inspect_err(|err| {
1884 error!(?err, "Unable to acquire write ticket!");
1885 })
1886 .ok()?
1887 } else {
1888 let fut = tokio::time::timeout(
1889 Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
1890 self.write_ticket.acquire(),
1891 );
1892
1893 match fut.await {
1894 Ok(Ok(ticket)) => ticket,
1895 Ok(Err(_)) => {
1896 error!("Failed to acquire write ticket, may be poisoned.");
1897 return None;
1898 }
1899 Err(_) => {
1900 error!("Failed to acquire write ticket, server is overloaded.");
1901 return None;
1902 }
1903 }
1904 };
1905
1906 let db_ticket = if cfg!(test) {
1910 self.db_tickets
1911 .try_acquire()
1912 .inspect_err(|err| {
1913 error!(?err, "Unable to acquire write db_ticket!");
1914 })
1915 .ok()?
1916 } else {
1917 self.db_tickets
1918 .acquire()
1919 .await
1920 .inspect_err(|err| {
1921 error!(?err, "Unable to acquire write db_ticket!");
1922 })
1923 .ok()?
1924 };
1925
1926 Some((write_ticket, db_ticket))
1927 }
1928
1929 pub async fn write(
1930 &self,
1931 curtime: Duration,
1932 ) -> Result<QueryServerWriteTransaction<'_>, OperationError> {
1933 let (write_ticket, db_ticket) = self
1934 .write_acquire_ticket()
1935 .await
1936 .ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
1937
1938 let be_txn = self.be.write()?;
1943
1944 let schema_write = self.schema.write();
1945 let d_info = self.d_info.write();
1946 let system_config = self.system_config.write();
1947 let phase = self.phase.write();
1948
1949 let mut cid = self.cid_max.write();
1950 *cid = Cid::new_lamport(cid.s_uuid, curtime, &cid.ts);
1952
1953 let trim_cid = cid.sub_secs(CHANGELOG_MAX_AGE)?;
1954
1955 Ok(QueryServerWriteTransaction {
1956 committed: false,
1963 phase,
1964 d_info,
1965 system_config,
1966 curtime,
1967 cid,
1968 trim_cid,
1969 be_txn,
1970 schema: schema_write,
1971 accesscontrols: self.accesscontrols.write(),
1972 changed_flags: ChangeFlag::empty(),
1973 changed_uuid: HashSet::new(),
1974 _db_ticket: db_ticket,
1975 _write_ticket: write_ticket,
1976 resolve_filter_cache: self.resolve_filter_cache.read(),
1977 resolve_filter_cache_clear: false,
1978 resolve_filter_cache_write: self.resolve_filter_cache.write(),
1979 dyngroup_cache: self.dyngroup_cache.write(),
1980 key_providers: self.key_providers.write(),
1981 })
1982 }
1983
1984 #[cfg(any(test, debug_assertions))]
1985 pub async fn clear_cache(&self) -> Result<(), OperationError> {
1986 let ct = duration_from_epoch_now();
1987 let mut w_txn = self.write(ct).await?;
1988 w_txn.clear_cache()?;
1989 w_txn.commit()
1990 }
1991
1992 pub async fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
1993 let current_time = duration_from_epoch_now();
1994 if self
1999 .write(current_time)
2000 .await
2001 .and_then(|mut txn| {
2002 txn.force_schema_reload();
2003 txn.commit()
2004 })
2005 .is_err()
2006 {
2007 return vec![Err(ConsistencyError::Unknown)];
2008 };
2009
2010 match self.read().await {
2011 Ok(mut r_txn) => r_txn.verify(),
2012 Err(_) => vec![Err(ConsistencyError::Unknown)],
2013 }
2014 }
2015}
2016
2017impl<'a> QueryServerWriteTransaction<'a> {
2018 pub(crate) fn get_server_uuid(&self) -> Uuid {
2019 self.cid.s_uuid
2021 }
2022
2023 pub(crate) fn reset_server_uuid(&mut self) -> Result<(), OperationError> {
2024 let s_uuid = self.be_txn.reset_db_s_uuid().map_err(|err| {
2025 error!(?err, "Failed to reset server replication uuid");
2026 err
2027 })?;
2028
2029 debug!(?s_uuid, "reset server replication uuid");
2030
2031 self.cid.s_uuid = s_uuid;
2032
2033 Ok(())
2034 }
2035
2036 pub(crate) fn get_curtime(&self) -> Duration {
2037 self.curtime
2038 }
2039
2040 pub(crate) fn get_cid(&self) -> &Cid {
2041 &self.cid
2042 }
2043
2044 pub(crate) fn get_key_providers_mut(&mut self) -> &mut KeyProvidersWriteTransaction<'a> {
2045 &mut self.key_providers
2046 }
2047
2048 pub(crate) fn get_dyngroup_cache(&mut self) -> &mut DynGroupCache {
2049 &mut self.dyngroup_cache
2050 }
2051
2052 pub fn domain_raise(&mut self, level: u32) -> Result<(), OperationError> {
2053 if level > DOMAIN_MAX_LEVEL {
2054 return Err(OperationError::MG0002RaiseDomainLevelExceedsMaximum);
2055 }
2056
2057 let modl = ModifyList::new_purge_and_set(Attribute::Version, Value::Uint32(level));
2058 let udi = PVUUID_DOMAIN_INFO.clone();
2059 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2060 self.internal_modify(&filt, &modl)
2061 }
2062
2063 pub fn domain_remigrate(&mut self, level: u32) -> Result<(), OperationError> {
2064 let mut_d_info = self.d_info.get_mut();
2065
2066 if level > mut_d_info.d_vers {
2067 return Ok(());
2069 } else if level < DOMAIN_MIN_REMIGRATION_LEVEL {
2070 return Err(OperationError::MG0001InvalidReMigrationLevel);
2071 };
2072
2073 info!(
2074 "Prepare to re-migrate from {} -> {}",
2075 level, mut_d_info.d_vers
2076 );
2077 mut_d_info.d_vers = level;
2078 self.changed_flags.insert(ChangeFlag::DOMAIN);
2079
2080 Ok(())
2081 }
2082
2083 #[instrument(level = "debug", skip_all)]
2084 pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> {
2085 let filt = filter!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
2088 let res = self.internal_search(filt).map_err(|e| {
2089 admin_error!("reload schema internal search failed {:?}", e);
2090 e
2091 })?;
2092 let attributetypes: Result<Vec<_>, _> =
2094 res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
2095
2096 let attributetypes = attributetypes.map_err(|e| {
2097 admin_error!("reload schema attributetypes {:?}", e);
2098 e
2099 })?;
2100
2101 self.schema.update_attributes(attributetypes).map_err(|e| {
2102 admin_error!("reload schema update attributetypes {:?}", e);
2103 e
2104 })?;
2105
2106 let filt = filter!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
2108 let res = self.internal_search(filt).map_err(|e| {
2109 admin_error!("reload schema internal search failed {:?}", e);
2110 e
2111 })?;
2112 let classtypes: Result<Vec<_>, _> = res.iter().map(|e| SchemaClass::try_from(e)).collect();
2114 let classtypes = classtypes.map_err(|e| {
2115 admin_error!("reload schema classtypes {:?}", e);
2116 e
2117 })?;
2118
2119 self.schema.update_classes(classtypes).map_err(|e| {
2120 admin_error!("reload schema update classtypes {:?}", e);
2121 e
2122 })?;
2123
2124 let valid_r = self.schema.validate();
2126
2127 if valid_r.is_empty() {
2129 trace!("Reloading idxmeta ...");
2131 self.be_txn
2132 .update_idxmeta(self.schema.reload_idxmeta())
2133 .map_err(|e| {
2134 admin_error!("reload schema update idxmeta {:?}", e);
2135 e
2136 })
2137 } else {
2138 admin_error!("Schema reload failed -> {:?}", valid_r);
2140 Err(OperationError::ConsistencyError(
2141 valid_r.into_iter().filter_map(|v| v.err()).collect(),
2142 ))
2143 }?;
2144
2145 self.resolve_filter_cache_clear = true;
2148
2149 DynGroup::reload(self)?;
2152
2153 Ok(())
2154 }
2155
2156 #[instrument(level = "debug", skip_all)]
2157 fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
2158 trace!("ACP reload started ...");
2166
2167 let filt = filter!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
2170
2171 let res = self.internal_search(filt).map_err(|e| {
2172 admin_error!(
2173 err = ?e,
2174 "reload accesscontrols internal search failed",
2175 );
2176 e
2177 })?;
2178
2179 let sync_agreement_map: HashMap<Uuid, BTreeSet<Attribute>> = res
2180 .iter()
2181 .filter_map(|e| {
2182 e.get_ava_as_iutf8(Attribute::SyncYieldAuthority)
2183 .map(|set| {
2184 let set: BTreeSet<_> =
2185 set.iter().map(|s| Attribute::from(s.as_str())).collect();
2186 (e.get_uuid(), set)
2187 })
2188 })
2189 .collect();
2190
2191 self.accesscontrols
2192 .update_sync_agreements(sync_agreement_map);
2193
2194 let filt = filter!(f_and!([
2196 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2197 f_eq(Attribute::Class, EntryClass::AccessControlSearch.into()),
2198 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2199 ]));
2200
2201 let res = self.internal_search(filt).map_err(|e| {
2202 admin_error!(
2203 err = ?e,
2204 "reload accesscontrols internal search failed",
2205 );
2206 e
2207 })?;
2208 let search_acps: Result<Vec<_>, _> = res
2209 .iter()
2210 .map(|e| AccessControlSearch::try_from(self, e))
2211 .collect();
2212
2213 let search_acps = search_acps.map_err(|e| {
2214 admin_error!(err = ?e, "Unable to parse search accesscontrols");
2215 e
2216 })?;
2217
2218 self.accesscontrols
2219 .update_search(search_acps)
2220 .map_err(|e| {
2221 admin_error!(err = ?e, "Failed to update search accesscontrols");
2222 e
2223 })?;
2224 let filt = filter!(f_and!([
2226 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2227 f_eq(Attribute::Class, EntryClass::AccessControlCreate.into()),
2228 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2229 ]));
2230
2231 let res = self.internal_search(filt).map_err(|e| {
2232 admin_error!(
2233 err = ?e,
2234 "reload accesscontrols internal search failed"
2235 );
2236 e
2237 })?;
2238 let create_acps: Result<Vec<_>, _> = res
2239 .iter()
2240 .map(|e| AccessControlCreate::try_from(self, e))
2241 .collect();
2242
2243 let create_acps = create_acps.map_err(|e| {
2244 admin_error!(err = ?e, "Unable to parse create accesscontrols");
2245 e
2246 })?;
2247
2248 self.accesscontrols
2249 .update_create(create_acps)
2250 .map_err(|e| {
2251 admin_error!(err = ?e, "Failed to update create accesscontrols");
2252 e
2253 })?;
2254 let filt = filter!(f_and!([
2256 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2257 f_eq(Attribute::Class, EntryClass::AccessControlModify.into()),
2258 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2259 ]));
2260
2261 let res = self.internal_search(filt).map_err(|e| {
2262 admin_error!("reload accesscontrols internal search failed {:?}", e);
2263 e
2264 })?;
2265 let modify_acps: Result<Vec<_>, _> = res
2266 .iter()
2267 .map(|e| AccessControlModify::try_from(self, e))
2268 .collect();
2269
2270 let modify_acps = modify_acps.map_err(|e| {
2271 admin_error!("Unable to parse modify accesscontrols {:?}", e);
2272 e
2273 })?;
2274
2275 self.accesscontrols
2276 .update_modify(modify_acps)
2277 .map_err(|e| {
2278 admin_error!("Failed to update modify accesscontrols {:?}", e);
2279 e
2280 })?;
2281 let filt = filter!(f_and!([
2283 f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
2284 f_eq(Attribute::Class, EntryClass::AccessControlDelete.into()),
2285 f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
2286 ]));
2287
2288 let res = self.internal_search(filt).map_err(|e| {
2289 admin_error!("reload accesscontrols internal search failed {:?}", e);
2290 e
2291 })?;
2292 let delete_acps: Result<Vec<_>, _> = res
2293 .iter()
2294 .map(|e| AccessControlDelete::try_from(self, e))
2295 .collect();
2296
2297 let delete_acps = delete_acps.map_err(|e| {
2298 admin_error!("Unable to parse delete accesscontrols {:?}", e);
2299 e
2300 })?;
2301
2302 self.accesscontrols.update_delete(delete_acps).map_err(|e| {
2303 admin_error!("Failed to update delete accesscontrols {:?}", e);
2304 e
2305 })
2306 }
2307
2308 #[instrument(level = "debug", skip_all)]
2309 pub(crate) fn reload_key_material(&mut self) -> Result<(), OperationError> {
2310 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
2311
2312 let res = self.internal_search(filt).map_err(|e| {
2313 admin_error!(
2314 err = ?e,
2315 "reload key providers internal search failed",
2316 );
2317 e
2318 })?;
2319
2320 let providers = res
2323 .iter()
2324 .map(|e| KeyProvider::try_from(e).and_then(|kp| kp.test().map(|()| kp)))
2325 .collect::<Result<Vec<_>, _>>()?;
2326
2327 self.key_providers.update_providers(providers)?;
2328
2329 let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
2330
2331 let res = self.internal_search(filt).map_err(|e| {
2332 admin_error!(
2333 err = ?e,
2334 "reload key objects internal search failed",
2335 );
2336 e
2337 })?;
2338
2339 res.iter()
2340 .try_for_each(|entry| self.key_providers.load_key_object(entry.as_ref()))
2341 }
2342
2343 #[instrument(level = "debug", skip_all)]
2344 pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
2345 let denied_names = self.get_sc_denied_names()?;
2346 let pw_badlist = self.get_sc_password_badlist()?;
2347
2348 let mut_system_config = self.system_config.get_mut();
2349 mut_system_config.denied_names = denied_names;
2350 mut_system_config.pw_badlist = pw_badlist;
2351 Ok(())
2352 }
2353
2354 #[instrument(level = "debug", skip_all)]
2356 pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
2357 let domain_info = self.internal_search_uuid(UUID_DOMAIN_INFO).map_err(|err| {
2358 error!(?err, "Error getting domain info");
2359 err
2360 })?;
2361
2362 let domain_info_version = domain_info
2363 .get_ava_single_uint32(Attribute::Version)
2364 .ok_or_else(|| {
2365 error!("domain info missing attribute version");
2366 OperationError::InvalidEntryState
2367 })?;
2368
2369 let domain_info_patch_level = domain_info
2370 .get_ava_single_uint32(Attribute::PatchLevel)
2371 .unwrap_or(0);
2372
2373 let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
2377 let domain_info_devel_taint = current_devel_flag
2378 || domain_info
2379 .get_ava_single_bool(Attribute::DomainDevelopmentTaint)
2380 .unwrap_or_default();
2381
2382 let domain_allow_easter_eggs = domain_info
2383 .get_ava_single_bool(Attribute::DomainAllowEasterEggs)
2384 .unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some());
2386
2387 let mut_d_info = self.d_info.get_mut();
2391 let previous_version = mut_d_info.d_vers;
2392 let previous_patch_level = mut_d_info.d_patch_level;
2393 mut_d_info.d_vers = domain_info_version;
2394 mut_d_info.d_patch_level = domain_info_patch_level;
2395 mut_d_info.d_devel_taint = domain_info_devel_taint;
2396 mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs;
2397
2398 if (previous_version == domain_info_version
2401 && previous_patch_level == domain_info_patch_level)
2402 || *self.phase < ServerPhase::DomainInfoReady
2403 {
2404 return Ok(());
2405 }
2406
2407 debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2408 debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2409
2410 if previous_version == DOMAIN_LEVEL_0 {
2414 debug!(
2415 "Server was just brought up, skipping migrations as we are already at target level"
2416 );
2417 return Ok(());
2418 }
2419
2420 if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL {
2421 error!("UNABLE TO PROCEED. You are attempting a Skip update which is NOT SUPPORTED. You must upgrade one-version of Kanidm at a time.");
2422 error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
2423 error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
2424 error!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
2425 return Err(OperationError::MG0008SkipUpgradeAttempted);
2426 }
2427
2428 if previous_version <= DOMAIN_LEVEL_8 && domain_info_version >= DOMAIN_LEVEL_9 {
2429 self.migrate_domain_8_to_9()?;
2431 }
2432
2433 if previous_patch_level < PATCH_LEVEL_2
2434 && domain_info_patch_level >= PATCH_LEVEL_2
2435 && domain_info_version == DOMAIN_LEVEL_9
2436 {
2437 self.migrate_domain_patch_level_2()?;
2438 }
2439
2440 if previous_version <= DOMAIN_LEVEL_9 && domain_info_version >= DOMAIN_LEVEL_10 {
2441 self.migrate_domain_9_to_10()?;
2443 }
2444
2445 if previous_version <= DOMAIN_LEVEL_10 && domain_info_version >= DOMAIN_LEVEL_11 {
2446 self.migrate_domain_10_to_11()?;
2448 }
2449
2450 if previous_version <= DOMAIN_LEVEL_11 && domain_info_version >= DOMAIN_LEVEL_12 {
2451 self.migrate_domain_11_to_12()?;
2453 }
2454
2455 debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
2459
2460 Ok(())
2461 }
2462
2463 #[instrument(level = "debug", skip_all)]
2465 pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
2466 let domain_entry = self.get_db_domain()?;
2467
2468 let domain_name = domain_entry
2469 .get_ava_single_iname(Attribute::DomainName)
2470 .map(str::to_string)
2471 .ok_or(OperationError::InvalidEntryState)?;
2472
2473 let display_name = domain_entry
2474 .get_ava_single_utf8(Attribute::DomainDisplayName)
2475 .map(str::to_string)
2476 .unwrap_or_else(|| format!("Kanidm {domain_name}"));
2477
2478 let domain_ldap_allow_unix_pw_bind = domain_entry
2479 .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
2480 .unwrap_or(true);
2481
2482 let domain_image = domain_entry.get_ava_single_image(Attribute::Image);
2483
2484 let domain_uuid = self.be_txn.get_db_d_uuid()?;
2485
2486 let mut_d_info = self.d_info.get_mut();
2487 mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind;
2488 if mut_d_info.d_uuid != domain_uuid {
2489 admin_warn!(
2490 "Using domain uuid from the database {} - was {} in memory",
2491 domain_name,
2492 mut_d_info.d_name,
2493 );
2494 mut_d_info.d_uuid = domain_uuid;
2495 }
2496 if mut_d_info.d_name != domain_name {
2497 admin_warn!(
2498 "Using domain name from the database {} - was {} in memory",
2499 domain_name,
2500 mut_d_info.d_name,
2501 );
2502 admin_warn!(
2503 "If you think this is an error, see https://kanidm.github.io/kanidm/master/domain_rename.html"
2504 );
2505 mut_d_info.d_name = domain_name;
2506 }
2507 mut_d_info.d_display = display_name;
2508 mut_d_info.d_image = domain_image;
2509 Ok(())
2510 }
2511
2512 pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2516 let modl = ModifyList::new_purge_and_set(
2517 Attribute::DomainDisplayName,
2518 Value::new_utf8(new_domain_name.to_string()),
2519 );
2520 let udi = PVUUID_DOMAIN_INFO.clone();
2521 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2522 self.internal_modify(&filt, &modl)
2523 }
2524
2525 pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
2538 let modl =
2539 ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name));
2540 let udi = PVUUID_DOMAIN_INFO.clone();
2541 let filt = filter_all!(f_eq(Attribute::Uuid, udi));
2542 self.internal_modify(&filt, &modl)
2543 }
2544
2545 pub fn reindex(&mut self, immediate: bool) -> Result<(), OperationError> {
2546 self.be_txn.reindex(immediate)
2550 }
2551
2552 fn force_schema_reload(&mut self) {
2553 self.changed_flags.insert(ChangeFlag::SCHEMA);
2554 }
2555
2556 fn force_domain_reload(&mut self) {
2557 self.changed_flags.insert(ChangeFlag::DOMAIN);
2558 }
2559
2560 pub(crate) fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> {
2561 self.be_txn.upgrade_reindex(v)
2562 }
2563
2564 #[inline]
2565 pub(crate) fn get_changed_app(&self) -> bool {
2566 self.changed_flags.contains(ChangeFlag::APPLICATION)
2567 }
2568
2569 #[inline]
2570 pub(crate) fn get_changed_oauth2(&self) -> bool {
2571 self.changed_flags.contains(ChangeFlag::OAUTH2)
2572 }
2573
2574 #[inline]
2575 pub(crate) fn clear_changed_oauth2(&mut self) {
2576 self.changed_flags.remove(ChangeFlag::OAUTH2)
2577 }
2578
2579 pub(crate) fn set_phase_bootstrap(&mut self) {
2582 *self.phase = ServerPhase::Bootstrap;
2583 }
2584
2585 pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
2587 if phase > *self.phase {
2589 *self.phase = phase
2590 }
2591 }
2592
2593 pub(crate) fn get_phase(&mut self) -> ServerPhase {
2594 *self.phase
2595 }
2596
2597 pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
2598 if self.changed_flags.contains(ChangeFlag::DOMAIN) {
2601 self.reload_domain_info_version()?;
2602 }
2603
2604 if self.changed_flags.contains(ChangeFlag::SCHEMA) {
2609 self.reload_schema()?;
2610
2611 if *self.phase >= ServerPhase::Running {
2615 self.reindex(false)?;
2616 self.reload_schema()?;
2617 }
2618 }
2619
2620 if self
2623 .changed_flags
2624 .intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
2625 {
2626 self.reload_key_material()?;
2627 }
2628
2629 if self
2637 .changed_flags
2638 .intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
2639 {
2640 self.reload_accesscontrols()?;
2641 } else {
2642 }
2647
2648 if self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG) {
2649 self.reload_system_config()?;
2650 }
2651
2652 if self.changed_flags.contains(ChangeFlag::DOMAIN) {
2653 self.reload_domain_info()?;
2654 }
2655
2656 self.changed_flags.remove(
2658 ChangeFlag::DOMAIN
2659 | ChangeFlag::SCHEMA
2660 | ChangeFlag::SYSTEM_CONFIG
2661 | ChangeFlag::ACP
2662 | ChangeFlag::SYNC_AGREEMENT
2663 | ChangeFlag::KEY_MATERIAL,
2664 );
2665
2666 Ok(())
2667 }
2668
2669 #[cfg(any(test, debug_assertions))]
2670 #[instrument(level = "debug", skip_all)]
2671 pub fn clear_cache(&mut self) -> Result<(), OperationError> {
2672 self.be_txn.clear_cache()
2673 }
2674
2675 #[instrument(level = "debug", name="qswt_commit" skip_all)]
2676 pub fn commit(mut self) -> Result<(), OperationError> {
2677 self.reload()?;
2678
2679 let QueryServerWriteTransaction {
2681 committed,
2682 phase,
2683 d_info,
2684 system_config,
2685 mut be_txn,
2686 schema,
2687 accesscontrols,
2688 cid,
2689 dyngroup_cache,
2690 key_providers,
2691 _db_ticket,
2693 _write_ticket,
2694 curtime: _,
2696 trim_cid: _,
2697 changed_flags,
2698 changed_uuid: _,
2699 resolve_filter_cache: _,
2700 resolve_filter_cache_clear,
2701 mut resolve_filter_cache_write,
2702 } = self;
2703 debug_assert!(!committed);
2704
2705 trace!(
2707 changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
2708 );
2709
2710 be_txn.set_db_ts_max(cid.ts)?;
2713 cid.commit();
2714
2715 if resolve_filter_cache_clear {
2717 resolve_filter_cache_write.clear();
2718 }
2719 resolve_filter_cache_write.commit();
2720
2721 schema
2725 .commit()
2726 .map(|_| d_info.commit())
2727 .map(|_| system_config.commit())
2728 .map(|_| phase.commit())
2729 .map(|_| dyngroup_cache.commit())
2730 .and_then(|_| key_providers.commit())
2731 .and_then(|_| accesscontrols.commit())
2732 .and_then(|_| be_txn.commit())
2733 }
2734
2735 pub(crate) fn get_txn_cid(&self) -> &Cid {
2736 &self.cid
2737 }
2738}
2739
2740#[cfg(test)]
2741mod tests {
2742 use crate::prelude::*;
2743 use kanidm_proto::scim_v1::{
2744 server::{ScimListResponse, ScimReference},
2745 JsonValue, ScimEntryGetQuery, ScimFilter,
2746 };
2747 use std::num::NonZeroU64;
2748
2749 #[qs_test]
2750 async fn test_name_to_uuid(server: &QueryServer) {
2751 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2752
2753 let t_uuid = Uuid::new_v4();
2754 assert!(server_txn
2755 .internal_create(vec![entry_init!(
2756 (Attribute::Class, EntryClass::Object.to_value()),
2757 (Attribute::Class, EntryClass::Account.to_value()),
2758 (Attribute::Class, EntryClass::Person.to_value()),
2759 (Attribute::Name, Value::new_iname("testperson1")),
2760 (Attribute::Uuid, Value::Uuid(t_uuid)),
2761 (Attribute::Description, Value::new_utf8s("testperson1")),
2762 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2763 ),])
2764 .is_ok());
2765
2766 let r1 = server_txn.name_to_uuid("testpers");
2768 assert!(r1.is_err());
2769 let r2 = server_txn.name_to_uuid("tEsTpErS");
2771 assert!(r2.is_err());
2772 let r3 = server_txn.name_to_uuid("testperson1");
2774 assert_eq!(r3, Ok(t_uuid));
2775 let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
2777 assert_eq!(r4, Ok(t_uuid));
2778 let r5 = server_txn.name_to_uuid("name=testperson1");
2780 assert_eq!(r5, Ok(t_uuid));
2781 let r6 = server_txn.name_to_uuid("name=testperson1,o=example");
2783 assert_eq!(r6, Ok(t_uuid));
2784 }
2785
2786 #[qs_test]
2787 async fn test_external_id_to_uuid(server: &QueryServer) {
2788 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2789
2790 let t_uuid = Uuid::new_v4();
2791 assert!(server_txn
2792 .internal_create(vec![entry_init!(
2793 (Attribute::Class, EntryClass::Object.to_value()),
2794 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
2795 (Attribute::Uuid, Value::Uuid(t_uuid)),
2796 (
2797 Attribute::SyncExternalId,
2798 Value::new_iutf8("uid=testperson")
2799 )
2800 ),])
2801 .is_ok());
2802
2803 let r1 = server_txn.sync_external_id_to_uuid("tobias");
2805 assert_eq!(r1, Ok(None));
2806 let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
2808 assert_eq!(r2, Ok(None));
2809 let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
2811 assert_eq!(r3, Ok(Some(t_uuid)));
2812 let r4 = server_txn.sync_external_id_to_uuid("uId=TeStPeRsOn");
2814 assert_eq!(r4, Ok(Some(t_uuid)));
2815 }
2816
2817 #[qs_test]
2818 async fn test_uuid_to_spn(server: &QueryServer) {
2819 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2820
2821 let e1 = entry_init!(
2822 (Attribute::Class, EntryClass::Object.to_value()),
2823 (Attribute::Class, EntryClass::Person.to_value()),
2824 (Attribute::Class, EntryClass::Account.to_value()),
2825 (Attribute::Name, Value::new_iname("testperson1")),
2826 (
2827 Attribute::Uuid,
2828 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
2829 ),
2830 (Attribute::Description, Value::new_utf8s("testperson1")),
2831 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2832 );
2833 let ce = CreateEvent::new_internal(vec![e1]);
2834 let cr = server_txn.create(&ce);
2835 assert!(cr.is_ok());
2836
2837 let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
2839 assert_eq!(r1, Ok(None));
2841 let r3 = server_txn.uuid_to_spn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
2843 println!("{r3:?}");
2844 assert_eq!(
2845 r3.unwrap().unwrap(),
2846 Value::new_spn_str("testperson1", "example.com")
2847 );
2848 let r4 = server_txn.uuid_to_spn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
2850 assert_eq!(
2851 r4.unwrap().unwrap(),
2852 Value::new_spn_str("testperson1", "example.com")
2853 );
2854 }
2855
2856 #[qs_test]
2857 async fn test_uuid_to_rdn(server: &QueryServer) {
2858 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2859
2860 let e1 = entry_init!(
2861 (Attribute::Class, EntryClass::Object.to_value()),
2862 (Attribute::Class, EntryClass::Person.to_value()),
2863 (Attribute::Class, EntryClass::Account.to_value()),
2864 (Attribute::Name, Value::new_iname("testperson1")),
2865 (
2866 Attribute::Uuid,
2867 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
2868 ),
2869 (Attribute::Description, Value::new_utf8s("testperson")),
2870 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2871 );
2872 let ce = CreateEvent::new_internal(vec![e1]);
2873 let cr = server_txn.create(&ce);
2874 assert!(cr.is_ok());
2875
2876 let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
2878 assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
2880 let r3 = server_txn.uuid_to_rdn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
2882 println!("{r3:?}");
2883 assert_eq!(r3.unwrap(), "spn=testperson1@example.com");
2884 let r4 = server_txn.uuid_to_rdn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
2886 assert_eq!(r4.unwrap(), "spn=testperson1@example.com");
2887 }
2888
2889 #[qs_test]
2890 async fn test_clone_value(server: &QueryServer) {
2891 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2892 let e1 = entry_init!(
2893 (Attribute::Class, EntryClass::Object.to_value()),
2894 (Attribute::Class, EntryClass::Account.to_value()),
2895 (Attribute::Class, EntryClass::Person.to_value()),
2896 (Attribute::Name, Value::new_iname("testperson1")),
2897 (
2898 Attribute::Uuid,
2899 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
2900 ),
2901 (Attribute::Description, Value::new_utf8s("testperson1")),
2902 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2903 );
2904 let ce = CreateEvent::new_internal(vec![e1]);
2905 let cr = server_txn.create(&ce);
2906 assert!(cr.is_ok());
2907
2908 let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
2910
2911 assert!(r1.is_err());
2912
2913 let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
2916
2917 assert!(r2.is_err());
2918
2919 let r3 = server_txn.clone_value(&Attribute::from("member"), "testperson1");
2921
2922 assert_eq!(
2923 r3,
2924 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
2925 );
2926
2927 let r4 = server_txn.clone_value(
2929 &Attribute::from("member"),
2930 "cc8e95b4-c24f-4d68-ba54-8bed76f63930",
2931 );
2932
2933 debug!("{:?}", r4);
2934 assert_eq!(
2935 r4,
2936 Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
2937 );
2938 }
2939
2940 #[qs_test]
2941 async fn test_dynamic_schema_class(server: &QueryServer) {
2942 let e1 = entry_init!(
2943 (Attribute::Class, EntryClass::Object.to_value()),
2944 (Attribute::Class, EntryClass::TestClass.to_value()),
2945 (Attribute::Name, Value::new_iname("testobj1")),
2946 (
2947 Attribute::Uuid,
2948 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
2949 )
2950 );
2951
2952 let e_cd = entry_init!(
2954 (Attribute::Class, EntryClass::Object.to_value()),
2955 (Attribute::Class, EntryClass::ClassType.to_value()),
2956 (Attribute::ClassName, EntryClass::TestClass.to_value()),
2957 (
2958 Attribute::Uuid,
2959 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
2960 ),
2961 (Attribute::Description, Value::new_utf8s("Test Class")),
2962 (Attribute::May, Value::from(Attribute::Name))
2963 );
2964 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2965 let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
2967 assert!(server_txn.create(&ce_class).is_ok());
2968 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
2970 assert!(server_txn.create(&ce_fail).is_err());
2971
2972 server_txn.commit().expect("should not fail");
2974
2975 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2977 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
2980 assert!(server_txn.create(&ce_work).is_ok());
2981
2982 server_txn.commit().expect("should not fail");
2984
2985 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2987 let de_class = DeleteEvent::new_internal_invalid(filter!(f_eq(
2989 Attribute::ClassName,
2990 EntryClass::TestClass.into()
2991 )));
2992 assert!(server_txn.delete(&de_class).is_ok());
2993 server_txn.commit().expect("should not fail");
2995
2996 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
2998 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3000 assert!(server_txn.create(&ce_fail).is_err());
3001 let testobj1 = server_txn
3003 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3004 .expect("failed");
3005 assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into()));
3006
3007 server_txn.commit().expect("should not fail");
3009 }
3011
3012 #[qs_test]
3013 async fn test_dynamic_schema_attr(server: &QueryServer) {
3014 let e1 = entry_init!(
3015 (Attribute::Class, EntryClass::Object.to_value()),
3016 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
3017 (Attribute::Name, Value::new_iname("testobj1")),
3018 (
3019 Attribute::Uuid,
3020 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3021 ),
3022 (Attribute::TestAttr, Value::new_utf8s("test"))
3023 );
3024
3025 let e_ad = entry_init!(
3027 (Attribute::Class, EntryClass::Object.to_value()),
3028 (Attribute::Class, EntryClass::AttributeType.to_value()),
3029 (
3030 Attribute::Uuid,
3031 Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
3032 ),
3033 (Attribute::AttributeName, Value::from(Attribute::TestAttr)),
3034 (Attribute::Description, Value::new_utf8s("Test Attribute")),
3035 (Attribute::MultiValue, Value::new_bool(false)),
3036 (Attribute::Unique, Value::new_bool(false)),
3037 (
3038 Attribute::Syntax,
3039 Value::new_syntaxs("UTF8STRING").expect("syntax")
3040 )
3041 );
3042
3043 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3044 let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
3046 assert!(server_txn.create(&ce_attr).is_ok());
3047 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3049 assert!(server_txn.create(&ce_fail).is_err());
3050
3051 server_txn.commit().expect("should not fail");
3053
3054 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3056 let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
3059 assert!(server_txn.create(&ce_work).is_ok());
3060
3061 server_txn.commit().expect("should not fail");
3063
3064 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3066 let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
3068 Attribute::AttributeName,
3069 PartialValue::from(Attribute::TestAttr)
3070 )));
3071 assert!(server_txn.delete(&de_attr).is_ok());
3072 server_txn.commit().expect("should not fail");
3074
3075 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3077 let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
3079 assert!(server_txn.create(&ce_fail).is_err());
3080 let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
3082 assert!(server_txn.internal_search(filt).is_err());
3083 let testobj1 = server_txn
3086 .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
3087 .expect("failed");
3088 assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test")));
3089
3090 server_txn.commit().expect("should not fail");
3091 }
3093
3094 #[qs_test]
3095 async fn test_scim_entry_structure(server: &QueryServer) {
3096 let mut read_txn = server.read().await.unwrap();
3097
3098 let entry = read_txn
3100 .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
3101 .unwrap();
3102
3103 let reduced = entry.as_ref().clone().into_reduced();
3105 let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
3106
3107 assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE);
3109 let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap();
3110 match name_scim {
3111 ScimValueKanidm::String(name) => {
3112 assert_eq!(name.clone(), "idm_people_self_name_write")
3113 }
3114 _ => {
3115 panic!("expected String, actual {name_scim:?}");
3116 }
3117 }
3118
3119 let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap();
3121 match entry_managed_by_scim {
3122 ScimValueKanidm::EntryReferences(managed_by) => {
3123 assert_eq!(
3124 managed_by.first().unwrap().clone(),
3125 ScimReference {
3126 uuid: UUID_IDM_ADMINS,
3127 value: "idm_admins@example.com".to_string()
3128 }
3129 )
3130 }
3131 _ => {
3132 panic!("expected EntryReference, actual {entry_managed_by_scim:?}");
3133 }
3134 }
3135
3136 let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap();
3137 match members_scim {
3138 ScimValueKanidm::EntryReferences(members) => {
3139 assert_eq!(
3140 members.first().unwrap().clone(),
3141 ScimReference {
3142 uuid: UUID_IDM_ALL_PERSONS,
3143 value: "idm_all_persons@example.com".to_string()
3144 }
3145 )
3146 }
3147 _ => {
3148 panic!("expected EntryReferences, actual {members_scim:?}");
3149 }
3150 }
3151 }
3152
3153 #[qs_test]
3154 async fn test_scim_effective_access_query(server: &QueryServer) {
3155 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3156
3157 let group_uuid = Uuid::new_v4();
3158 let e1 = entry_init!(
3159 (Attribute::Class, EntryClass::Object.to_value()),
3160 (Attribute::Class, EntryClass::Group.to_value()),
3161 (Attribute::Name, Value::new_iname("testgroup")),
3162 (Attribute::Uuid, Value::Uuid(group_uuid))
3163 );
3164
3165 assert!(server_txn.internal_create(vec![e1]).is_ok());
3166 assert!(server_txn.commit().is_ok());
3167
3168 let mut server_txn = server.read().await.unwrap();
3171
3172 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3173 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3174
3175 let query = ScimEntryGetQuery {
3176 ext_access_check: true,
3177 ..Default::default()
3178 };
3179
3180 let scim_entry = server_txn
3181 .scim_entry_id_get_ext(group_uuid, EntryClass::Group, query, idm_admin_ident)
3182 .unwrap();
3183
3184 let ext_access_check = scim_entry.ext_access_check.unwrap();
3185
3186 trace!(?ext_access_check);
3187
3188 assert!(ext_access_check.delete);
3189 assert!(ext_access_check.search.check(&Attribute::DirectMemberOf));
3190 assert!(ext_access_check.search.check(&Attribute::MemberOf));
3191 assert!(ext_access_check.search.check(&Attribute::Name));
3192 assert!(ext_access_check.modify_present.check(&Attribute::Name));
3193 assert!(ext_access_check.modify_remove.check(&Attribute::Name));
3194 }
3195
3196 #[qs_test]
3197 async fn test_scim_basic_search_ext_query(server: &QueryServer) {
3198 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3199
3200 let group_uuid = Uuid::new_v4();
3201 let e1 = entry_init!(
3202 (Attribute::Class, EntryClass::Object.to_value()),
3203 (Attribute::Class, EntryClass::Group.to_value()),
3204 (Attribute::Name, Value::new_iname("testgroup")),
3205 (Attribute::Uuid, Value::Uuid(group_uuid))
3206 );
3207
3208 assert!(server_txn.internal_create(vec![e1]).is_ok());
3209 assert!(server_txn.commit().is_ok());
3210
3211 let mut server_txn = server.read().await.unwrap();
3213
3214 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3215 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3216
3217 let filter = ScimFilter::And(
3218 Box::new(ScimFilter::Equal(
3219 Attribute::Class.into(),
3220 EntryClass::Group.into(),
3221 )),
3222 Box::new(ScimFilter::Equal(
3223 Attribute::Uuid.into(),
3224 JsonValue::String(group_uuid.to_string()),
3225 )),
3226 );
3227
3228 let base: ScimListResponse = server_txn
3229 .scim_search_ext(idm_admin_ident, filter, ScimEntryGetQuery::default())
3230 .unwrap();
3231
3232 assert_eq!(base.resources.len(), 1);
3233 assert_eq!(base.total_results, 1);
3234 assert_eq!(base.items_per_page, None);
3236 assert_eq!(base.start_index, None);
3237 assert_eq!(base.resources[0].header.id, group_uuid);
3238 }
3239
3240 #[qs_test]
3241 async fn test_scim_basic_search_ext_query_with_sort(server: &QueryServer) {
3242 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
3243
3244 for i in (1..4).rev() {
3245 let e1 = entry_init!(
3246 (Attribute::Class, EntryClass::Object.to_value()),
3247 (Attribute::Class, EntryClass::Group.to_value()),
3248 (
3249 Attribute::Name,
3250 Value::new_iname(format!("testgroup{i}").as_str())
3251 )
3252 );
3253 assert!(server_txn.internal_create(vec![e1]).is_ok());
3254 }
3255
3256 assert!(server_txn.commit().is_ok());
3257
3258 let mut server_txn = server.read().await.unwrap();
3260
3261 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
3262 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
3263
3264 let filter = ScimFilter::And(
3265 Box::new(ScimFilter::Equal(
3266 Attribute::Class.into(),
3267 EntryClass::Group.into(),
3268 )),
3269 Box::new(ScimFilter::StartsWith(
3270 Attribute::Name.into(),
3271 JsonValue::String("testgroup".into()),
3272 )),
3273 );
3274
3275 let base: ScimListResponse = server_txn
3276 .scim_search_ext(
3277 idm_admin_ident.clone(),
3278 filter.clone(),
3279 ScimEntryGetQuery {
3280 sort_by: Some(Attribute::Name),
3281 ..Default::default()
3282 },
3283 )
3284 .unwrap();
3285
3286 assert_eq!(base.resources.len(), 3);
3287 assert_eq!(base.total_results, 3);
3288 assert_eq!(base.items_per_page, None);
3290 assert_eq!(base.start_index, None);
3291
3292 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3293 base.resources[0].attrs.get(&Attribute::Name)
3294 else {
3295 panic!("Invalid data in attribute.");
3296 };
3297 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3298 base.resources[1].attrs.get(&Attribute::Name)
3299 else {
3300 panic!("Invalid data in attribute.");
3301 };
3302 let Some(ScimValueKanidm::String(testgroup_name_2)) =
3303 base.resources[2].attrs.get(&Attribute::Name)
3304 else {
3305 panic!("Invalid data in attribute.");
3306 };
3307
3308 assert!(testgroup_name_0 < testgroup_name_1);
3309 assert!(testgroup_name_0 < testgroup_name_2);
3310 assert!(testgroup_name_1 < testgroup_name_2);
3311
3312 let base: ScimListResponse = server_txn
3315 .scim_search_ext(
3316 idm_admin_ident.clone(),
3317 filter.clone(),
3318 ScimEntryGetQuery {
3319 count: NonZeroU64::new(1),
3320 ..Default::default()
3321 },
3322 )
3323 .unwrap();
3324
3325 assert_eq!(base.resources.len(), 1);
3326 assert_eq!(base.total_results, 3);
3327 assert_eq!(base.items_per_page, NonZeroU64::new(1));
3329 assert_eq!(base.start_index, NonZeroU64::new(1));
3330
3331 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3332 base.resources[0].attrs.get(&Attribute::Name)
3333 else {
3334 panic!("Invalid data in attribute.");
3335 };
3336 assert_eq!(testgroup_name_0, "testgroup3");
3338
3339 let base: ScimListResponse = server_txn
3342 .scim_search_ext(
3343 idm_admin_ident,
3344 filter.clone(),
3345 ScimEntryGetQuery {
3346 sort_by: Some(Attribute::Name),
3347 count: NonZeroU64::new(2),
3348 start_index: NonZeroU64::new(2),
3349 ..Default::default()
3350 },
3351 )
3352 .unwrap();
3353
3354 assert_eq!(base.resources.len(), 2);
3355 assert_eq!(base.total_results, 3);
3356 assert_eq!(base.items_per_page, NonZeroU64::new(2));
3357 assert_eq!(base.start_index, NonZeroU64::new(2));
3358
3359 let Some(ScimValueKanidm::String(testgroup_name_0)) =
3360 base.resources[0].attrs.get(&Attribute::Name)
3361 else {
3362 panic!("Invalid data in attribute.");
3363 };
3364 let Some(ScimValueKanidm::String(testgroup_name_1)) =
3365 base.resources[1].attrs.get(&Attribute::Name)
3366 else {
3367 panic!("Invalid data in attribute.");
3368 };
3369 assert_eq!(testgroup_name_0, "testgroup2");
3371 assert_eq!(testgroup_name_1, "testgroup3");
3372 }
3373}