use self::access::{
profiles::{
AccessControlCreate, AccessControlDelete, AccessControlModify, AccessControlSearch,
},
AccessControls, AccessControlsReadTransaction, AccessControlsTransaction,
AccessControlsWriteTransaction,
};
use self::keys::{
KeyObject, KeyProvider, KeyProviders, KeyProvidersReadTransaction, KeyProvidersTransaction,
KeyProvidersWriteTransaction,
};
use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction};
use crate::filter::{
Filter, FilterInvalid, FilterValid, FilterValidResolved, ResolveFilterCache,
ResolveFilterCacheReadTxn,
};
use crate::plugins::dyngroup::{DynGroup, DynGroupCache};
use crate::plugins::Plugins;
use crate::prelude::*;
use crate::repl::cid::Cid;
use crate::repl::proto::ReplRuvRange;
use crate::repl::ruv::ReplicationUpdateVectorTransaction;
use crate::schema::{
Schema, SchemaAttribute, SchemaClass, SchemaReadTransaction, SchemaTransaction,
SchemaWriteTransaction,
};
use crate::value::{CredentialType, EXTRACT_VAL_DN};
use crate::valueset::uuid_to_proto_string;
use crate::valueset::ScimValueIntermediate;
use crate::valueset::*;
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn};
use concread::cowcell::*;
use hashbrown::{HashMap, HashSet};
use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
use kanidm_proto::scim_v1::server::ScimOAuth2ClaimMap;
use kanidm_proto::scim_v1::server::ScimOAuth2ScopeMap;
use kanidm_proto::scim_v1::server::ScimReference;
use kanidm_proto::scim_v1::JsonValue;
use kanidm_proto::scim_v1::ScimEntryGetQuery;
use std::collections::BTreeSet;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::{Semaphore, SemaphorePermit};
use tracing::trace;
pub(crate) mod access;
pub mod batch_modify;
pub mod create;
pub mod delete;
pub mod identity;
pub(crate) mod keys;
pub(crate) mod migrations;
pub mod modify;
pub(crate) mod recycle;
pub mod scim;
const RESOLVE_FILTER_CACHE_MAX: usize = 256;
const RESOLVE_FILTER_CACHE_LOCAL: usize = 8;
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq)]
pub(crate) enum ServerPhase {
Bootstrap,
SchemaReady,
DomainInfoReady,
Running,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DomainInfo {
pub(crate) d_uuid: Uuid,
pub(crate) d_name: String,
pub(crate) d_display: String,
pub(crate) d_vers: DomainVersion,
pub(crate) d_patch_level: u32,
pub(crate) d_devel_taint: bool,
pub(crate) d_ldap_allow_unix_pw_bind: bool,
pub(crate) d_allow_easter_eggs: bool,
d_image: Option<ImageValue>,
}
impl DomainInfo {
pub fn name(&self) -> &str {
self.d_name.as_str()
}
pub fn display_name(&self) -> &str {
self.d_display.as_str()
}
pub fn devel_taint(&self) -> bool {
self.d_devel_taint
}
pub fn image(&self) -> Option<&ImageValue> {
self.d_image.as_ref()
}
pub fn has_custom_image(&self) -> bool {
self.d_image.is_some()
}
pub fn allow_easter_eggs(&self) -> bool {
self.d_allow_easter_eggs
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SystemConfig {
pub(crate) denied_names: HashSet<String>,
pub(crate) pw_badlist: HashSet<String>,
}
#[derive(Clone)]
pub struct QueryServer {
phase: Arc<CowCell<ServerPhase>>,
pub(crate) d_info: Arc<CowCell<DomainInfo>>,
system_config: Arc<CowCell<SystemConfig>>,
be: Backend,
schema: Arc<Schema>,
accesscontrols: Arc<AccessControls>,
db_tickets: Arc<Semaphore>,
read_tickets: Arc<Semaphore>,
write_ticket: Arc<Semaphore>,
resolve_filter_cache: Arc<ResolveFilterCache>,
dyngroup_cache: Arc<CowCell<DynGroupCache>>,
cid_max: Arc<CowCell<Cid>>,
key_providers: Arc<KeyProviders>,
}
pub struct QueryServerReadTransaction<'a> {
be_txn: BackendReadTransaction<'a>,
pub(crate) d_info: CowCellReadTxn<DomainInfo>,
system_config: CowCellReadTxn<SystemConfig>,
schema: SchemaReadTransaction,
accesscontrols: AccessControlsReadTransaction<'a>,
key_providers: KeyProvidersReadTransaction,
_db_ticket: SemaphorePermit<'a>,
_read_ticket: SemaphorePermit<'a>,
resolve_filter_cache: ResolveFilterCacheReadTxn<'a>,
trim_cid: Cid,
}
unsafe impl Sync for QueryServerReadTransaction<'_> {}
unsafe impl Send for QueryServerReadTransaction<'_> {}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug)]
pub struct ChangeFlag: u32 {
const SCHEMA = 0b0000_0001;
const ACP = 0b0000_0010;
const OAUTH2 = 0b0000_0100;
const DOMAIN = 0b0000_1000;
const SYSTEM_CONFIG = 0b0001_0000;
const SYNC_AGREEMENT = 0b0010_0000;
const KEY_MATERIAL = 0b0100_0000;
const APPLICATION = 0b1000_0000;
}
}
pub struct QueryServerWriteTransaction<'a> {
committed: bool,
phase: CowCellWriteTxn<'a, ServerPhase>,
d_info: CowCellWriteTxn<'a, DomainInfo>,
system_config: CowCellWriteTxn<'a, SystemConfig>,
curtime: Duration,
cid: CowCellWriteTxn<'a, Cid>,
trim_cid: Cid,
pub(crate) be_txn: BackendWriteTransaction<'a>,
pub(crate) schema: SchemaWriteTransaction<'a>,
accesscontrols: AccessControlsWriteTransaction<'a>,
key_providers: KeyProvidersWriteTransaction<'a>,
pub(super) changed_flags: ChangeFlag,
pub(super) changed_uuid: HashSet<Uuid>,
_db_ticket: SemaphorePermit<'a>,
_write_ticket: SemaphorePermit<'a>,
resolve_filter_cache: ARCacheReadTxn<
'a,
(IdentityId, Arc<Filter<FilterValid>>),
Arc<Filter<FilterValidResolved>>,
(),
>,
dyngroup_cache: CowCellWriteTxn<'a, DynGroupCache>,
}
impl QueryServerWriteTransaction<'_> {
pub(crate) fn trim_cid(&self) -> &Cid {
&self.trim_cid
}
}
pub trait QueryServerTransaction<'a> {
type BackendTransactionType: BackendTransaction;
fn get_be_txn(&mut self) -> &mut Self::BackendTransactionType;
type SchemaTransactionType: SchemaTransaction;
fn get_schema<'b>(&self) -> &'b Self::SchemaTransactionType;
type AccessControlsTransactionType: AccessControlsTransaction<'a>;
fn get_accesscontrols(&self) -> &Self::AccessControlsTransactionType;
type KeyProvidersTransactionType: KeyProvidersTransaction;
fn get_key_providers(&self) -> &Self::KeyProvidersTransactionType;
fn pw_badlist(&self) -> &HashSet<String>;
fn denied_names(&self) -> &HashSet<String>;
fn get_domain_version(&self) -> DomainVersion;
fn get_domain_patch_level(&self) -> u32;
fn get_domain_development_taint(&self) -> bool;
fn get_domain_uuid(&self) -> Uuid;
fn get_domain_name(&self) -> &str;
fn get_domain_display_name(&self) -> &str;
fn get_domain_image_value(&self) -> Option<ImageValue>;
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a>;
fn get_resolve_filter_cache_and_be_txn(
&mut self,
) -> (
&mut Self::BackendTransactionType,
&mut ResolveFilterCacheReadTxn<'a>,
);
#[instrument(level = "debug", skip_all)]
fn search_ext(
&mut self,
se: &SearchEvent,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
let entries = self.search(se)?;
let access = self.get_accesscontrols();
access
.search_filter_entry_attributes(se, entries)
.map_err(|e| {
admin_error!(?e, "Failed to filter entry attributes");
e
})
}
#[instrument(level = "debug", skip_all)]
fn search(
&mut self,
se: &SearchEvent,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
if se.ident.is_internal() {
trace!(internal_filter = ?se.filter, "search");
} else {
security_info!(initiator = %se.ident, "search");
admin_debug!(external_filter = ?se.filter, "search");
}
let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
let idxmeta = be_txn.get_idxmeta_ref();
let vfr = se
.filter
.resolve(&se.ident, Some(idxmeta), Some(resolve_filter_cache))
.map_err(|e| {
admin_error!(?e, "search filter resolve failure");
e
})?;
let lims = se.ident.limits();
let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
admin_error!(?e, "backend failure");
OperationError::Backend
})?;
let access = self.get_accesscontrols();
access.search_filter_entries(se, res).map_err(|e| {
admin_error!(?e, "Unable to access filter entries");
e
})
}
#[instrument(level = "debug", skip_all)]
fn exists(&mut self, ee: &ExistsEvent) -> Result<bool, OperationError> {
let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
let idxmeta = be_txn.get_idxmeta_ref();
let vfr = ee
.filter
.resolve(&ee.ident, Some(idxmeta), Some(resolve_filter_cache))
.map_err(|e| {
admin_error!(?e, "Failed to resolve filter");
e
})?;
let lims = ee.ident.limits();
if ee.ident.is_internal() {
be_txn.exists(lims, &vfr).map_err(|e| {
admin_error!(?e, "backend failure");
OperationError::Backend
})
} else {
let res = self.get_be_txn().search(lims, &vfr).map_err(|e| {
admin_error!(?e, "backend failure");
OperationError::Backend
})?;
let access = self.get_accesscontrols();
access
.filter_entries(&ee.ident, &ee.filter_orig, res)
.map_err(|e| {
admin_error!(?e, "Unable to access filter entries");
e
})
.map(|entries| !entries.is_empty())
}
}
fn name_to_uuid(&mut self, name: &str) -> Result<Uuid, OperationError> {
let work = EXTRACT_VAL_DN
.captures(name)
.and_then(|caps| caps.name("val"))
.map(|v| v.as_str().to_lowercase())
.ok_or(OperationError::InvalidValueState)?;
Uuid::parse_str(&work).or_else(|_| {
self.get_be_txn()
.name2uuid(&work)?
.ok_or(OperationError::NoMatchingEntries)
})
}
fn sync_external_id_to_uuid(
&mut self,
external_id: &str,
) -> Result<Option<Uuid>, OperationError> {
Uuid::parse_str(external_id).map(Some).or_else(|_| {
let lname = external_id.to_lowercase();
self.get_be_txn().externalid2uuid(lname.as_str())
})
}
fn uuid_to_spn(&mut self, uuid: Uuid) -> Result<Option<Value>, OperationError> {
let r = self.get_be_txn().uuid2spn(uuid)?;
if let Some(ref n) = r {
debug_assert!(n.is_spn() || n.is_iname());
}
Ok(r)
}
fn uuid_to_rdn(&mut self, uuid: Uuid) -> Result<String, OperationError> {
self.get_be_txn()
.uuid2rdn(uuid)
.map(|v| v.unwrap_or_else(|| format!("uuid={}", uuid.as_hyphenated())))
}
#[instrument(level = "debug", skip_all)]
fn internal_exists(&mut self, filter: Filter<FilterInvalid>) -> Result<bool, OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let ee = ExistsEvent::new_internal(f_valid);
self.exists(&ee)
}
#[instrument(level = "debug", skip_all)]
fn internal_search(
&mut self,
filter: Filter<FilterInvalid>,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let se = SearchEvent::new_internal(f_valid);
self.search(&se)
}
#[instrument(level = "debug", skip_all)]
fn impersonate_search_valid(
&mut self,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
event: &Identity,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
self.search(&se)
}
fn impersonate_search_ext_valid(
&mut self,
f_valid: Filter<FilterValid>,
f_intent_valid: Filter<FilterValid>,
event: &Identity,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
self.search_ext(&se)
}
fn impersonate_search(
&mut self,
filter: Filter<FilterInvalid>,
filter_intent: Filter<FilterInvalid>,
event: &Identity,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let f_intent_valid = filter_intent
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
self.impersonate_search_valid(f_valid, f_intent_valid, event)
}
#[instrument(level = "debug", skip_all)]
fn impersonate_search_ext(
&mut self,
filter: Filter<FilterInvalid>,
filter_intent: Filter<FilterInvalid>,
event: &Identity,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
let f_valid = filter
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let f_intent_valid = filter_intent
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
self.impersonate_search_ext_valid(f_valid, f_intent_valid, event)
}
#[instrument(level = "debug", skip_all)]
fn internal_search_uuid(
&mut self,
uuid: Uuid,
) -> Result<Arc<EntrySealedCommitted>, OperationError> {
let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
let f_valid = filter.validate(self.get_schema()).map_err(|e| {
error!(?e, "Filter Validate - SchemaViolation");
OperationError::SchemaViolation(e)
})?;
let se = SearchEvent::new_internal(f_valid);
let mut vs = self.search(&se)?;
match vs.pop() {
Some(entry) if vs.is_empty() => Ok(entry),
_ => Err(OperationError::NoMatchingEntries),
}
}
#[instrument(level = "debug", skip_all)]
fn internal_search_all_uuid(
&mut self,
uuid: Uuid,
) -> Result<Arc<EntrySealedCommitted>, OperationError> {
let filter = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
let f_valid = filter.validate(self.get_schema()).map_err(|e| {
error!(?e, "Filter Validate - SchemaViolation");
OperationError::SchemaViolation(e)
})?;
let se = SearchEvent::new_internal(f_valid);
let mut vs = self.search(&se)?;
match vs.pop() {
Some(entry) if vs.is_empty() => Ok(entry),
_ => Err(OperationError::NoMatchingEntries),
}
}
#[instrument(level = "debug", skip_all)]
fn internal_search_conflict_uuid(
&mut self,
uuid: Uuid,
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
let filter = filter_all!(f_and(vec![
f_eq(Attribute::SourceUuid, PartialValue::Uuid(uuid)),
f_eq(Attribute::Class, EntryClass::Conflict.into())
]));
let f_valid = filter.validate(self.get_schema()).map_err(|e| {
error!(?e, "Filter Validate - SchemaViolation");
OperationError::SchemaViolation(e)
})?;
let se = SearchEvent::new_internal(f_valid);
self.search(&se)
}
#[instrument(level = "debug", skip_all)]
fn impersonate_search_ext_uuid(
&mut self,
uuid: Uuid,
event: &Identity,
) -> Result<Entry<EntryReduced, EntryCommitted>, OperationError> {
let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
let mut vs = self.impersonate_search_ext(filter, filter_intent, event)?;
match vs.pop() {
Some(entry) if vs.is_empty() => Ok(entry),
_ => {
if vs.is_empty() {
Err(OperationError::NoMatchingEntries)
} else {
Err(OperationError::UniqueConstraintViolation)
}
}
}
}
#[instrument(level = "debug", skip_all)]
fn impersonate_search_uuid(
&mut self,
uuid: Uuid,
event: &Identity,
) -> Result<Arc<EntrySealedCommitted>, OperationError> {
let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
let mut vs = self.impersonate_search(filter, filter_intent, event)?;
match vs.pop() {
Some(entry) if vs.is_empty() => Ok(entry),
_ => Err(OperationError::NoMatchingEntries),
}
}
fn clone_value(&mut self, attr: &Attribute, value: &str) -> Result<Value, OperationError> {
let schema = self.get_schema();
match schema.get_attributes().get(attr) {
Some(schema_a) => {
match schema_a.syntax {
SyntaxType::Utf8String => Ok(Value::new_utf8(value.to_string())),
SyntaxType::Utf8StringInsensitive => Ok(Value::new_iutf8(value)),
SyntaxType::Utf8StringIname => Ok(Value::new_iname(value)),
SyntaxType::Boolean => Value::new_bools(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
SyntaxType::SyntaxId => Value::new_syntaxs(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
SyntaxType::IndexId => Value::new_indexes(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
SyntaxType::CredentialType => CredentialType::try_from(value)
.map(Value::CredentialType)
.map_err(|()| OperationError::InvalidAttribute("Invalid CredentialType syntax".to_string())),
SyntaxType::Uuid => {
let un = self
.name_to_uuid(value)
.unwrap_or(UUID_DOES_NOT_EXIST);
Ok(Value::Uuid(un))
}
SyntaxType::ReferenceUuid => {
let un = self
.name_to_uuid(value)
.unwrap_or(UUID_DOES_NOT_EXIST);
Ok(Value::Refer(un))
}
SyntaxType::JsonFilter => Value::new_json_filter_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
SyntaxType::Image => Value::new_image(value),
SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
SyntaxType::Uint32 => Value::new_uint32_str(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid NsUniqueId syntax".to_string())),
SyntaxType::DateTime => Value::new_datetime_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
SyntaxType::EmailAddress => Value::new_email_address_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Email Address syntax".to_string())),
SyntaxType::Url => Value::new_url_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Url (whatwg/url) syntax".to_string())),
SyntaxType::OauthScope => Value::new_oauthscope(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Oauth Scope syntax".to_string())),
SyntaxType::WebauthnAttestationCaList => Value::new_webauthn_attestation_ca_list(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Webauthn Attestation CA List".to_string())),
SyntaxType::OauthScopeMap => Err(OperationError::InvalidAttribute("Oauth Scope Maps can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::OauthClaimMap => Err(OperationError::InvalidAttribute("Oauth Claim Maps can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute("Private Binary Values can not be supplied through modification".to_string())),
SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute("AttestedPasskey Values can not be supplied through modification".to_string())),
SyntaxType::Session => Err(OperationError::InvalidAttribute("Session Values can not be supplied through modification".to_string())),
SyntaxType::ApiToken => Err(OperationError::InvalidAttribute("ApiToken Values can not be supplied through modification".to_string())),
SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())),
SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())),
SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute("Oauth2Session Values can not be supplied through modification".to_string())),
SyntaxType::UiHint => UiHint::from_str(value)
.map(Value::UiHint)
.map_err(|()| OperationError::InvalidAttribute("Invalid uihint syntax".to_string())),
SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute("TotpSecret Values can not be supplied through modification".to_string())),
SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute("Audit logs are generated and not able to be set.".to_string())),
SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute("Ec keys are generated and not able to be set.".to_string())),
SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute("Internal keys are generated and not able to be set.".to_string())),
SyntaxType::HexString => Value::new_hex_string_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid hex string syntax".to_string())),
SyntaxType::Certificate => Value::new_certificate_s(value)
.ok_or_else(|| OperationError::InvalidAttribute("Invalid x509 certificate syntax".to_string())),
SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute("ApplicationPassword values can not be supplied through modification".to_string())),
}
}
None => {
Err(OperationError::InvalidAttributeName(attr.to_string()))
}
}
}
fn clone_partialvalue(
&mut self,
attr: &Attribute,
value: &str,
) -> Result<PartialValue, OperationError> {
let schema = self.get_schema();
match schema.get_attributes().get(attr) {
Some(schema_a) => {
match schema_a.syntax {
SyntaxType::Utf8String | SyntaxType::TotpSecret => {
Ok(PartialValue::new_utf8(value.to_string()))
}
SyntaxType::Utf8StringInsensitive
| SyntaxType::JwsKeyEs256
| SyntaxType::JwsKeyRs256 => Ok(PartialValue::new_iutf8(value)),
SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
}),
SyntaxType::SyntaxId => PartialValue::new_syntaxs(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
}),
SyntaxType::IndexId => PartialValue::new_indexes(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Index syntax".to_string())
}),
SyntaxType::CredentialType => CredentialType::try_from(value)
.map(PartialValue::CredentialType)
.map_err(|()| {
OperationError::InvalidAttribute(
"Invalid credentialtype syntax".to_string(),
)
}),
SyntaxType::Uuid => {
let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
Ok(PartialValue::Uuid(un))
}
SyntaxType::ReferenceUuid
| SyntaxType::OauthScopeMap
| SyntaxType::Session
| SyntaxType::ApiToken
| SyntaxType::Oauth2Session
| SyntaxType::ApplicationPassword => {
let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
Ok(PartialValue::Refer(un))
}
SyntaxType::OauthClaimMap => self
.name_to_uuid(value)
.map(PartialValue::Refer)
.or_else(|_| Ok(PartialValue::new_iutf8(value))),
SyntaxType::JsonFilter => {
PartialValue::new_json_filter_s(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
})
}
SyntaxType::Credential => Ok(PartialValue::new_credential_tag(value)),
SyntaxType::SecretUtf8String => Ok(PartialValue::new_secret_str()),
SyntaxType::SshKey => Ok(PartialValue::new_sshkey_tag_s(value)),
SyntaxType::SecurityPrincipalName => {
PartialValue::new_spn_s(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid spn syntax".to_string())
})
}
SyntaxType::Uint32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
}),
SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid cid syntax".to_string())
}),
SyntaxType::NsUniqueId => Ok(PartialValue::new_nsuniqueid_s(value)),
SyntaxType::DateTime => PartialValue::new_datetime_s(value).ok_or_else(|| {
OperationError::InvalidAttribute(
"Invalid DateTime (rfc3339) syntax".to_string(),
)
}),
SyntaxType::EmailAddress => Ok(PartialValue::new_email_address_s(value)),
SyntaxType::Url => PartialValue::new_url_s(value).ok_or_else(|| {
OperationError::InvalidAttribute(
"Invalid Url (whatwg/url) syntax".to_string(),
)
}),
SyntaxType::OauthScope => Ok(PartialValue::new_oauthscope(value)),
SyntaxType::PrivateBinary => Ok(PartialValue::PrivateBinary),
SyntaxType::IntentToken => PartialValue::new_intenttoken_s(value.to_string())
.ok_or_else(|| {
OperationError::InvalidAttribute(
"Invalid Intent Token ID (uuid) syntax".to_string(),
)
}),
SyntaxType::Passkey => PartialValue::new_passkey_s(value).ok_or_else(|| {
OperationError::InvalidAttribute("Invalid Passkey UUID syntax".to_string())
}),
SyntaxType::AttestedPasskey => PartialValue::new_attested_passkey_s(value)
.ok_or_else(|| {
OperationError::InvalidAttribute(
"Invalid AttestedPasskey UUID syntax".to_string(),
)
}),
SyntaxType::UiHint => UiHint::from_str(value)
.map(PartialValue::UiHint)
.map_err(|()| {
OperationError::InvalidAttribute("Invalid uihint syntax".to_string())
}),
SyntaxType::AuditLogString => Ok(PartialValue::new_utf8s(value)),
SyntaxType::EcKeyPrivate => Ok(PartialValue::SecretValue),
SyntaxType::Image => Ok(PartialValue::new_utf8s(value)),
SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
"Invalid - unable to query attestation CA list".to_string(),
)),
SyntaxType::HexString | SyntaxType::KeyInternal | SyntaxType::Certificate => {
PartialValue::new_hex_string_s(value).ok_or_else(|| {
OperationError::InvalidAttribute(
"Invalid syntax, expected hex string".to_string(),
)
})
}
}
}
None => {
Err(OperationError::InvalidAttributeName(attr.to_string()))
}
}
}
fn resolve_scim_interim(
&mut self,
scim_value_intermediate: ScimValueIntermediate,
) -> Result<Option<ScimValueKanidm>, OperationError> {
match scim_value_intermediate {
ScimValueIntermediate::References(uuids) => {
let scim_references = uuids
.into_iter()
.map(|uuid| {
self.uuid_to_spn(uuid)
.and_then(|maybe_value| {
maybe_value.ok_or(OperationError::InvalidValueState)
})
.map(|value| ScimReference {
uuid,
value: value.to_proto_string_clone(),
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(ScimValueKanidm::EntryReferences(scim_references)))
}
ScimValueIntermediate::Oauth2ClaimMap(unresolved_maps) => {
let scim_claim_maps = unresolved_maps
.into_iter()
.map(
|UnresolvedScimValueOauth2ClaimMap {
group_uuid,
claim,
join_char,
values,
}| {
self.uuid_to_spn(group_uuid)
.and_then(|maybe_value| {
maybe_value.ok_or(OperationError::InvalidValueState)
})
.map(|value| ScimOAuth2ClaimMap {
group: value.to_proto_string_clone(),
group_uuid,
claim,
join_char,
values,
})
},
)
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(ScimValueKanidm::OAuth2ClaimMap(scim_claim_maps)))
}
ScimValueIntermediate::Oauth2ScopeMap(unresolved_maps) => {
let scim_claim_maps = unresolved_maps
.into_iter()
.map(|UnresolvedScimValueOauth2ScopeMap { group_uuid, scopes }| {
self.uuid_to_spn(group_uuid)
.and_then(|maybe_value| {
maybe_value.ok_or(OperationError::InvalidValueState)
})
.map(|value| ScimOAuth2ScopeMap {
group: value.to_proto_string_clone(),
group_uuid,
scopes,
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(ScimValueKanidm::OAuth2ScopeMap(scim_claim_maps)))
}
}
}
fn resolve_scim_json_put(
&mut self,
attr: &Attribute,
value: Option<JsonValue>,
) -> Result<Option<ValueSet>, OperationError> {
let schema = self.get_schema();
let Some(schema_a) = schema.get_attributes().get(attr) else {
return Err(OperationError::InvalidAttributeName(attr.to_string()));
};
let Some(value) = value else {
return Ok(None);
};
let resolve_status = match schema_a.syntax {
SyntaxType::Utf8String => ValueSetUtf8::from_scim_json_put(value),
SyntaxType::Utf8StringInsensitive => ValueSetIutf8::from_scim_json_put(value),
SyntaxType::Uuid => ValueSetUuid::from_scim_json_put(value),
SyntaxType::Boolean => ValueSetBool::from_scim_json_put(value),
SyntaxType::SyntaxId => ValueSetSyntax::from_scim_json_put(value),
SyntaxType::IndexId => ValueSetIndex::from_scim_json_put(value),
SyntaxType::ReferenceUuid => ValueSetRefer::from_scim_json_put(value),
SyntaxType::Utf8StringIname => ValueSetIname::from_scim_json_put(value),
SyntaxType::NsUniqueId => ValueSetNsUniqueId::from_scim_json_put(value),
SyntaxType::DateTime => ValueSetDateTime::from_scim_json_put(value),
SyntaxType::EmailAddress => ValueSetEmailAddress::from_scim_json_put(value),
SyntaxType::Url => ValueSetUrl::from_scim_json_put(value),
SyntaxType::OauthScope => ValueSetOauthScope::from_scim_json_put(value),
SyntaxType::OauthScopeMap => ValueSetOauthScopeMap::from_scim_json_put(value),
SyntaxType::OauthClaimMap => ValueSetOauthClaimMap::from_scim_json_put(value),
SyntaxType::UiHint => ValueSetUiHint::from_scim_json_put(value),
SyntaxType::CredentialType => ValueSetCredentialType::from_scim_json_put(value),
SyntaxType::Certificate => ValueSetCertificate::from_scim_json_put(value),
SyntaxType::SshKey => ValueSetSshKey::from_scim_json_put(value),
SyntaxType::Uint32 => ValueSetUint32::from_scim_json_put(value),
SyntaxType::JsonFilter => Err(OperationError::InvalidAttribute(
"Json Filters are not able to be set.".to_string(),
)),
SyntaxType::HexString => Err(OperationError::InvalidAttribute(
"Hex strings are not able to be set.".to_string(),
)),
SyntaxType::Image => Err(OperationError::InvalidAttribute(
"Images are not able to be set.".to_string(),
)),
SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
"Webauthn Attestation Ca Lists are not able to be set.".to_string(),
)),
SyntaxType::Credential => Err(OperationError::InvalidAttribute(
"Credentials are not able to be set.".to_string(),
)),
SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute(
"Secrets are not able to be set.".to_string(),
)),
SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute(
"SPNs are not able to be set.".to_string(),
)),
SyntaxType::Cid => Err(OperationError::InvalidAttribute(
"CIDs are not able to be set.".to_string(),
)),
SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute(
"Private Binaries are not able to be set.".to_string(),
)),
SyntaxType::IntentToken => Err(OperationError::InvalidAttribute(
"Intent Tokens are not able to be set.".to_string(),
)),
SyntaxType::Passkey => Err(OperationError::InvalidAttribute(
"Passkeys are not able to be set.".to_string(),
)),
SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute(
"Attested Passkeys are not able to be set.".to_string(),
)),
SyntaxType::Session => Err(OperationError::InvalidAttribute(
"Sessions are not able to be set.".to_string(),
)),
SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute(
"Jws ES256 Private Keys are not able to be set.".to_string(),
)),
SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute(
"Jws RS256 Private Keys are not able to be set.".to_string(),
)),
SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute(
"Sessions are not able to be set.".to_string(),
)),
SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute(
"TOTP Secrets are not able to be set.".to_string(),
)),
SyntaxType::ApiToken => Err(OperationError::InvalidAttribute(
"API Tokens are not able to be set.".to_string(),
)),
SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute(
"Audit Strings are not able to be set.".to_string(),
)),
SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute(
"EC Private Keys are not able to be set.".to_string(),
)),
SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute(
"Key Internal Structures are not able to be set.".to_string(),
)),
SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute(
"Application Passwords are not able to be set.".to_string(),
)),
}?;
match resolve_status {
ValueSetResolveStatus::Resolved(vs) => Ok(vs),
ValueSetResolveStatus::NeedsResolution(vs_inter) => {
self.resolve_valueset_intermediate(vs_inter)
}
}
.map(Some)
}
fn resolve_valueset_intermediate(
&mut self,
vs_inter: ValueSetIntermediate,
) -> Result<ValueSet, OperationError> {
match vs_inter {
ValueSetIntermediate::References {
mut resolved,
unresolved,
} => {
for value in unresolved {
let un = self.name_to_uuid(value.as_str()).unwrap_or_else(|_| {
warn!(
?value,
"Value can not be resolved to a uuid - assuming it does not exist."
);
UUID_DOES_NOT_EXIST
});
resolved.insert(un);
}
let vs = ValueSetRefer::from_set(resolved);
Ok(vs)
}
ValueSetIntermediate::Oauth2ClaimMap {
mut resolved,
unresolved,
} => {
resolved.extend(unresolved.into_iter().map(
|UnresolvedValueSetOauth2ClaimMap {
group_name,
claim,
join_char,
claim_values,
}| {
let group_uuid =
self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
warn!(
?group_name,
"Value can not be resolved to a uuid - assuming it does not exist."
);
UUID_DOES_NOT_EXIST
});
ResolvedValueSetOauth2ClaimMap {
group_uuid,
claim,
join_char,
claim_values,
}
},
));
let vs = ValueSetOauthClaimMap::from_set(resolved);
Ok(vs)
}
ValueSetIntermediate::Oauth2ScopeMap {
mut resolved,
unresolved,
} => {
resolved.extend(unresolved.into_iter().map(
|UnresolvedValueSetOauth2ScopeMap { group_name, scopes }| {
let group_uuid =
self.name_to_uuid(group_name.as_str()).unwrap_or_else(|_| {
warn!(
?group_name,
"Value can not be resolved to a uuid - assuming it does not exist."
);
UUID_DOES_NOT_EXIST
});
ResolvedValueSetOauth2ScopeMap { group_uuid, scopes }
},
));
let vs = ValueSetOauthScopeMap::from_set(resolved);
Ok(vs)
}
}
}
fn resolve_valueset(&mut self, value: &ValueSet) -> Result<Vec<String>, OperationError> {
if let Some(r_set) = value.as_refer_set() {
let v: Result<Vec<_>, _> = r_set
.iter()
.copied()
.map(|ur| {
let nv = self.uuid_to_spn(ur)?;
match nv {
Some(v) => Ok(v.to_proto_string_clone()),
None => Ok(uuid_to_proto_string(ur)),
}
})
.collect();
v
} else if let Some(r_map) = value.as_oauthscopemap() {
let v: Result<Vec<_>, _> = r_map
.iter()
.map(|(u, m)| {
let nv = self.uuid_to_spn(*u)?;
let u = match nv {
Some(v) => v.to_proto_string_clone(),
None => uuid_to_proto_string(*u),
};
Ok(format!("{u}: {m:?}"))
})
.collect();
v
} else if let Some(r_map) = value.as_oauthclaim_map() {
let mut v = Vec::with_capacity(0);
for (claim_name, mapping) in r_map.iter() {
for (group_ref, claims) in mapping.values() {
let join_char = mapping.join().to_str();
let nv = self.uuid_to_spn(*group_ref)?;
let resolved_id = match nv {
Some(v) => v.to_proto_string_clone(),
None => uuid_to_proto_string(*group_ref),
};
let joined = str_concat!(claims, ",");
v.push(format!(
"{}:{}:{}:{:?}",
claim_name, resolved_id, join_char, joined
))
}
}
Ok(v)
} else {
let v: Vec<_> = value.to_proto_string_clone_iter().collect();
Ok(v)
}
}
fn resolve_valueset_ldap(
&mut self,
value: &ValueSet,
basedn: &str,
) -> Result<Vec<Vec<u8>>, OperationError> {
if let Some(r_set) = value.as_refer_set() {
let v: Result<Vec<_>, _> = r_set
.iter()
.copied()
.map(|ur| {
let rdn = self.uuid_to_rdn(ur)?;
Ok(format!("{rdn},{basedn}").into_bytes())
})
.collect();
v
} else if let Some(key_iter) = value.as_sshpubkey_string_iter() {
let v: Vec<_> = key_iter.map(|s| s.into_bytes()).collect();
Ok(v)
} else {
let v: Vec<_> = value
.to_proto_string_clone_iter()
.map(|s| s.into_bytes())
.collect();
Ok(v)
}
}
fn get_db_domain(&mut self) -> Result<Arc<EntrySealedCommitted>, OperationError> {
self.internal_search_uuid(UUID_DOMAIN_INFO)
}
fn get_domain_key_object_handle(&self) -> Result<Arc<KeyObject>, OperationError> {
#[cfg(test)]
if self.get_domain_version() < DOMAIN_LEVEL_6 {
return Ok(crate::server::keys::KeyObjectInternal::new_test());
};
self.get_key_providers()
.get_key_object_handle(UUID_DOMAIN_INFO)
.ok_or(OperationError::KP0031KeyObjectNotFound)
}
fn get_domain_es256_private_key(&mut self) -> Result<Vec<u8>, OperationError> {
self.internal_search_uuid(UUID_DOMAIN_INFO)
.and_then(|e| {
e.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
.map(|s| s.to_vec())
.ok_or(OperationError::InvalidEntryState)
})
.map_err(|e| {
admin_error!(?e, "Error getting domain es256 key");
e
})
}
fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| {
entry
.get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
.unwrap_or(true)
})
}
fn get_sc_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
.map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) {
Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
None => HashSet::default(),
})
.map_err(|e| {
error!(
?e,
"Failed to retrieve password badlist from system configuration"
);
e
})
}
fn get_sc_denied_names(&mut self) -> Result<HashSet<String>, OperationError> {
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
.map(|e| match e.get_ava_iter_iname(Attribute::DeniedName) {
Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
None => HashSet::default(),
})
.map_err(|e| {
error!(
?e,
"Failed to retrieve denied names from system configuration"
);
e
})
}
fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
self.internal_search(filter!(f_eq(
Attribute::Class,
EntryClass::OAuth2ResourceServer.into(),
)))
}
fn get_applications_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
self.internal_search(filter!(f_eq(
Attribute::Class,
EntryClass::Application.into(),
)))
}
#[instrument(level = "debug", skip_all)]
fn consumer_get_state(&mut self) -> Result<ReplRuvRange, OperationError> {
let domain_uuid = self.get_domain_uuid();
let ruv_snapshot = self.get_be_txn().get_ruv();
ruv_snapshot
.current_ruv_range()
.map(|ranges| ReplRuvRange::V1 {
domain_uuid,
ranges,
})
}
}
impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
type AccessControlsTransactionType = AccessControlsReadTransaction<'a>;
type BackendTransactionType = BackendReadTransaction<'a>;
type SchemaTransactionType = SchemaReadTransaction;
type KeyProvidersTransactionType = KeyProvidersReadTransaction;
fn get_be_txn(&mut self) -> &mut BackendReadTransaction<'a> {
&mut self.be_txn
}
fn get_schema<'b>(&self) -> &'b SchemaReadTransaction {
unsafe {
let s = (&self.schema) as *const _;
&*s
}
}
fn get_accesscontrols(&self) -> &AccessControlsReadTransaction<'a> {
&self.accesscontrols
}
fn get_key_providers(&self) -> &KeyProvidersReadTransaction {
&self.key_providers
}
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
&mut self.resolve_filter_cache
}
fn get_resolve_filter_cache_and_be_txn(
&mut self,
) -> (
&mut BackendReadTransaction<'a>,
&mut ResolveFilterCacheReadTxn<'a>,
) {
(&mut self.be_txn, &mut self.resolve_filter_cache)
}
fn pw_badlist(&self) -> &HashSet<String> {
&self.system_config.pw_badlist
}
fn denied_names(&self) -> &HashSet<String> {
&self.system_config.denied_names
}
fn get_domain_version(&self) -> DomainVersion {
self.d_info.d_vers
}
fn get_domain_patch_level(&self) -> u32 {
self.d_info.d_patch_level
}
fn get_domain_development_taint(&self) -> bool {
self.d_info.d_devel_taint
}
fn get_domain_uuid(&self) -> Uuid {
self.d_info.d_uuid
}
fn get_domain_name(&self) -> &str {
&self.d_info.d_name
}
fn get_domain_display_name(&self) -> &str {
&self.d_info.d_display
}
fn get_domain_image_value(&self) -> Option<ImageValue> {
self.d_info.d_image.clone()
}
}
impl QueryServerReadTransaction<'_> {
pub(crate) fn trim_cid(&self) -> &Cid {
&self.trim_cid
}
pub fn domain_info(&mut self) -> Result<ProtoDomainInfo, OperationError> {
let d_info = &self.d_info;
Ok(ProtoDomainInfo {
name: d_info.d_name.clone(),
displayname: d_info.d_display.clone(),
uuid: d_info.d_uuid,
level: d_info.d_vers,
})
}
pub(crate) fn verify(&mut self) -> Vec<Result<(), ConsistencyError>> {
let be_errs = self.get_be_txn().verify();
if !be_errs.is_empty() {
return be_errs;
}
let sc_errs = self.get_schema().validate();
if !sc_errs.is_empty() {
return sc_errs;
}
let idx_errs = self.get_be_txn().verify_indexes();
if !idx_errs.is_empty() {
return idx_errs;
}
let mut results = Vec::with_capacity(0);
let schema = self.get_schema();
let filt_all = filter!(f_pres(Attribute::Class));
let all_entries = match self.internal_search(filt_all) {
Ok(a) => a,
Err(_e) => return vec![Err(ConsistencyError::QueryServerSearchFailure)],
};
for e in all_entries {
e.verify(schema, &mut results)
}
self.get_be_txn().verify_ruv(&mut results);
Plugins::run_verify(self, &mut results);
results
}
#[instrument(level = "debug", skip_all)]
pub fn scim_entry_id_get_ext(
&mut self,
uuid: Uuid,
class: EntryClass,
query: ScimEntryGetQuery,
ident: Identity,
) -> Result<ScimEntryKanidm, OperationError> {
let filter_intent = filter!(f_and!([
f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)),
f_eq(Attribute::Class, class.into())
]));
let f_intent_valid = filter_intent
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let f_valid = f_intent_valid.clone().into_ignore_hidden();
let r_attrs = query
.attributes
.map(|attr_set| attr_set.into_iter().collect());
let se = SearchEvent {
ident,
filter: f_valid,
filter_orig: f_intent_valid,
attrs: r_attrs,
};
let mut vs = self.search_ext(&se)?;
match vs.pop() {
Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
_ => {
if vs.is_empty() {
Err(OperationError::NoMatchingEntries)
} else {
Err(OperationError::UniqueConstraintViolation)
}
}
}
}
}
impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
type AccessControlsTransactionType = AccessControlsWriteTransaction<'a>;
type BackendTransactionType = BackendWriteTransaction<'a>;
type SchemaTransactionType = SchemaWriteTransaction<'a>;
type KeyProvidersTransactionType = KeyProvidersWriteTransaction<'a>;
fn get_be_txn(&mut self) -> &mut BackendWriteTransaction<'a> {
&mut self.be_txn
}
fn get_schema<'b>(&self) -> &'b SchemaWriteTransaction<'a> {
unsafe {
let s = (&self.schema) as *const _;
&*s
}
}
fn get_accesscontrols(&self) -> &AccessControlsWriteTransaction<'a> {
&self.accesscontrols
}
fn get_key_providers(&self) -> &KeyProvidersWriteTransaction<'a> {
&self.key_providers
}
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
&mut self.resolve_filter_cache
}
fn get_resolve_filter_cache_and_be_txn(
&mut self,
) -> (
&mut BackendWriteTransaction<'a>,
&mut ResolveFilterCacheReadTxn<'a>,
) {
(&mut self.be_txn, &mut self.resolve_filter_cache)
}
fn pw_badlist(&self) -> &HashSet<String> {
&self.system_config.pw_badlist
}
fn denied_names(&self) -> &HashSet<String> {
&self.system_config.denied_names
}
fn get_domain_version(&self) -> DomainVersion {
self.d_info.d_vers
}
fn get_domain_patch_level(&self) -> u32 {
self.d_info.d_patch_level
}
fn get_domain_development_taint(&self) -> bool {
self.d_info.d_devel_taint
}
fn get_domain_uuid(&self) -> Uuid {
self.d_info.d_uuid
}
fn get_domain_name(&self) -> &str {
&self.d_info.d_name
}
fn get_domain_display_name(&self) -> &str {
&self.d_info.d_display
}
fn get_domain_image_value(&self) -> Option<ImageValue> {
self.d_info.d_image.clone()
}
}
impl QueryServer {
pub fn new(
be: Backend,
schema: Schema,
domain_name: String,
curtime: Duration,
) -> Result<Self, OperationError> {
let (s_uuid, d_uuid, ts_max) = {
let mut wr = be.write()?;
let s_uuid = wr.get_db_s_uuid()?;
let d_uuid = wr.get_db_d_uuid()?;
let ts_max = wr.get_db_ts_max(curtime)?;
wr.commit()?;
(s_uuid, d_uuid, ts_max)
};
let pool_size = be.get_pool_size();
debug!("Server UUID -> {:?}", s_uuid);
debug!("Domain UUID -> {:?}", d_uuid);
debug!("Domain Name -> {:?}", domain_name);
let d_info = Arc::new(CowCell::new(DomainInfo {
d_uuid,
d_vers: DOMAIN_LEVEL_0,
d_patch_level: 0,
d_name: domain_name.clone(),
d_display: domain_name,
d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(),
d_ldap_allow_unix_pw_bind: false,
d_allow_easter_eggs: false,
d_image: None,
}));
let cid = Cid::new_lamport(s_uuid, curtime, &ts_max);
let cid_max = Arc::new(CowCell::new(cid));
let system_config = Arc::new(CowCell::new(SystemConfig::default()));
let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
let resolve_filter_cache = Arc::new(
ARCacheBuilder::new()
.set_size(RESOLVE_FILTER_CACHE_MAX, RESOLVE_FILTER_CACHE_LOCAL)
.set_reader_quiesce(true)
.build()
.ok_or_else(|| {
error!("Failed to build filter resolve cache");
OperationError::DB0003FilterResolveCacheBuild
})?,
);
let key_providers = Arc::new(KeyProviders::default());
debug_assert!(pool_size > 0);
let read_ticket_pool = std::cmp::max(pool_size - 1, 1);
Ok(QueryServer {
phase,
d_info,
system_config,
be,
schema: Arc::new(schema),
accesscontrols: Arc::new(AccessControls::default()),
db_tickets: Arc::new(Semaphore::new(pool_size as usize)),
read_tickets: Arc::new(Semaphore::new(read_ticket_pool as usize)),
write_ticket: Arc::new(Semaphore::new(1)),
resolve_filter_cache,
dyngroup_cache,
cid_max,
key_providers,
})
}
pub fn try_quiesce(&self) {
self.be.try_quiesce();
self.accesscontrols.try_quiesce();
self.resolve_filter_cache.try_quiesce();
}
#[instrument(level = "debug", skip_all)]
async fn read_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
let read_ticket = if cfg!(test) {
self.read_tickets
.try_acquire()
.inspect_err(|err| {
error!(?err, "Unable to acquire read ticket!");
})
.ok()?
} else {
let fut = tokio::time::timeout(
Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
self.read_tickets.acquire(),
);
match fut.await {
Ok(Ok(ticket)) => ticket,
Ok(Err(_)) => {
error!("Failed to acquire read ticket, may be poisoned.");
return None;
}
Err(_) => {
error!("Failed to acquire read ticket, server is overloaded.");
return None;
}
}
};
let db_ticket = if cfg!(test) {
self.db_tickets
.try_acquire()
.inspect_err(|err| {
error!(?err, "Unable to acquire database ticket!");
})
.ok()?
} else {
self.db_tickets
.acquire()
.await
.inspect_err(|err| {
error!(?err, "Unable to acquire database ticket!");
})
.ok()?
};
Some((read_ticket, db_ticket))
}
pub async fn read(&self) -> Result<QueryServerReadTransaction<'_>, OperationError> {
let (read_ticket, db_ticket) = self
.read_acquire_ticket()
.await
.ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
let schema = self.schema.read();
let cid_max = self.cid_max.read();
let trim_cid = cid_max.sub_secs(CHANGELOG_MAX_AGE)?;
let be_txn = self.be.read()?;
Ok(QueryServerReadTransaction {
be_txn,
schema,
d_info: self.d_info.read(),
system_config: self.system_config.read(),
accesscontrols: self.accesscontrols.read(),
key_providers: self.key_providers.read(),
_db_ticket: db_ticket,
_read_ticket: read_ticket,
resolve_filter_cache: self.resolve_filter_cache.read(),
trim_cid,
})
}
#[instrument(level = "debug", skip_all)]
async fn write_acquire_ticket(&self) -> Option<(SemaphorePermit<'_>, SemaphorePermit<'_>)> {
let write_ticket = if cfg!(test) {
self.write_ticket
.try_acquire()
.inspect_err(|err| {
error!(?err, "Unable to acquire write ticket!");
})
.ok()?
} else {
let fut = tokio::time::timeout(
Duration::from_millis(DB_LOCK_ACQUIRE_TIMEOUT_MILLIS),
self.write_ticket.acquire(),
);
match fut.await {
Ok(Ok(ticket)) => ticket,
Ok(Err(_)) => {
error!("Failed to acquire write ticket, may be poisoned.");
return None;
}
Err(_) => {
error!("Failed to acquire write ticket, server is overloaded.");
return None;
}
}
};
let db_ticket = if cfg!(test) {
self.db_tickets
.try_acquire()
.inspect_err(|err| {
error!(?err, "Unable to acquire write db_ticket!");
})
.ok()?
} else {
self.db_tickets
.acquire()
.await
.inspect_err(|err| {
error!(?err, "Unable to acquire write db_ticket!");
})
.ok()?
};
Some((write_ticket, db_ticket))
}
pub async fn write(
&self,
curtime: Duration,
) -> Result<QueryServerWriteTransaction<'_>, OperationError> {
let (write_ticket, db_ticket) = self
.write_acquire_ticket()
.await
.ok_or(OperationError::DatabaseLockAcquisitionTimeout)?;
let be_txn = self.be.write()?;
let schema_write = self.schema.write();
let d_info = self.d_info.write();
let system_config = self.system_config.write();
let phase = self.phase.write();
let mut cid = self.cid_max.write();
*cid = Cid::new_lamport(cid.s_uuid, curtime, &cid.ts);
let trim_cid = cid.sub_secs(CHANGELOG_MAX_AGE)?;
Ok(QueryServerWriteTransaction {
committed: false,
phase,
d_info,
system_config,
curtime,
cid,
trim_cid,
be_txn,
schema: schema_write,
accesscontrols: self.accesscontrols.write(),
changed_flags: ChangeFlag::empty(),
changed_uuid: HashSet::new(),
_db_ticket: db_ticket,
_write_ticket: write_ticket,
resolve_filter_cache: self.resolve_filter_cache.read(),
dyngroup_cache: self.dyngroup_cache.write(),
key_providers: self.key_providers.write(),
})
}
#[cfg(any(test, debug_assertions))]
pub async fn clear_cache(&self) -> Result<(), OperationError> {
let ct = duration_from_epoch_now();
let mut w_txn = self.write(ct).await?;
w_txn.clear_cache()?;
w_txn.commit()
}
pub async fn verify(&self) -> Vec<Result<(), ConsistencyError>> {
match self.read().await {
Ok(mut r_txn) => r_txn.verify(),
Err(_) => vec![Err(ConsistencyError::Unknown)],
}
}
}
impl<'a> QueryServerWriteTransaction<'a> {
pub(crate) fn get_server_uuid(&self) -> Uuid {
self.cid.s_uuid
}
pub(crate) fn reset_server_uuid(&mut self) -> Result<(), OperationError> {
let s_uuid = self.be_txn.reset_db_s_uuid().map_err(|err| {
error!(?err, "Failed to reset server replication uuid");
err
})?;
debug!(?s_uuid, "reset server replication uuid");
self.cid.s_uuid = s_uuid;
Ok(())
}
pub(crate) fn get_curtime(&self) -> Duration {
self.curtime
}
pub(crate) fn get_cid(&self) -> &Cid {
&self.cid
}
pub(crate) fn get_key_providers_mut(&mut self) -> &mut KeyProvidersWriteTransaction<'a> {
&mut self.key_providers
}
pub(crate) fn get_dyngroup_cache(&mut self) -> &mut DynGroupCache {
&mut self.dyngroup_cache
}
pub fn domain_raise(&mut self, level: u32) -> Result<(), OperationError> {
if level > DOMAIN_MAX_LEVEL {
return Err(OperationError::MG0002RaiseDomainLevelExceedsMaximum);
}
let modl = ModifyList::new_purge_and_set(Attribute::Version, Value::Uint32(level));
let udi = PVUUID_DOMAIN_INFO.clone();
let filt = filter_all!(f_eq(Attribute::Uuid, udi));
self.internal_modify(&filt, &modl)
}
pub fn domain_remigrate(&mut self, level: u32) -> Result<(), OperationError> {
let mut_d_info = self.d_info.get_mut();
if level > mut_d_info.d_vers {
return Ok(());
} else if level < DOMAIN_MIN_REMIGRATION_LEVEL {
return Err(OperationError::MG0001InvalidReMigrationLevel);
};
info!(
"Prepare to re-migrate from {} -> {}",
level, mut_d_info.d_vers
);
mut_d_info.d_vers = level;
self.changed_flags.insert(ChangeFlag::DOMAIN);
Ok(())
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> {
let filt = filter!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
let res = self.internal_search(filt).map_err(|e| {
admin_error!("reload schema internal search failed {:?}", e);
e
})?;
let attributetypes: Result<Vec<_>, _> =
res.iter().map(|e| SchemaAttribute::try_from(e)).collect();
let attributetypes = attributetypes.map_err(|e| {
admin_error!("reload schema attributetypes {:?}", e);
e
})?;
self.schema.update_attributes(attributetypes).map_err(|e| {
admin_error!("reload schema update attributetypes {:?}", e);
e
})?;
let filt = filter!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
let res = self.internal_search(filt).map_err(|e| {
admin_error!("reload schema internal search failed {:?}", e);
e
})?;
let classtypes: Result<Vec<_>, _> = res.iter().map(|e| SchemaClass::try_from(e)).collect();
let classtypes = classtypes.map_err(|e| {
admin_error!("reload schema classtypes {:?}", e);
e
})?;
self.schema.update_classes(classtypes).map_err(|e| {
admin_error!("reload schema update classtypes {:?}", e);
e
})?;
let valid_r = self.schema.validate();
if valid_r.is_empty() {
trace!("Reloading idxmeta ...");
self.be_txn
.update_idxmeta(self.schema.reload_idxmeta())
.map_err(|e| {
admin_error!("reload schema update idxmeta {:?}", e);
e
})
} else {
admin_error!("Schema reload failed -> {:?}", valid_r);
Err(OperationError::ConsistencyError(
valid_r.into_iter().filter_map(|v| v.err()).collect(),
))
}?;
if *self.phase >= ServerPhase::SchemaReady {
DynGroup::reload(self)?;
}
Ok(())
}
#[instrument(level = "debug", skip_all)]
fn reload_accesscontrols(&mut self) -> Result<(), OperationError> {
trace!("ACP reload started ...");
let filt = filter!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
let res = self.internal_search(filt).map_err(|e| {
admin_error!(
err = ?e,
"reload accesscontrols internal search failed",
);
e
})?;
let sync_agreement_map: HashMap<Uuid, BTreeSet<Attribute>> = res
.iter()
.filter_map(|e| {
e.get_ava_as_iutf8(Attribute::SyncYieldAuthority)
.map(|set| {
let set: BTreeSet<_> =
set.iter().map(|s| Attribute::from(s.as_str())).collect();
(e.get_uuid(), set)
})
})
.collect();
self.accesscontrols
.update_sync_agreements(sync_agreement_map);
let filt = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
f_eq(Attribute::Class, EntryClass::AccessControlSearch.into()),
f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
]));
let res = self.internal_search(filt).map_err(|e| {
admin_error!(
err = ?e,
"reload accesscontrols internal search failed",
);
e
})?;
let search_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlSearch::try_from(self, e))
.collect();
let search_acps = search_acps.map_err(|e| {
admin_error!(err = ?e, "Unable to parse search accesscontrols");
e
})?;
self.accesscontrols
.update_search(search_acps)
.map_err(|e| {
admin_error!(err = ?e, "Failed to update search accesscontrols");
e
})?;
let filt = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
f_eq(Attribute::Class, EntryClass::AccessControlCreate.into()),
f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
]));
let res = self.internal_search(filt).map_err(|e| {
admin_error!(
err = ?e,
"reload accesscontrols internal search failed"
);
e
})?;
let create_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlCreate::try_from(self, e))
.collect();
let create_acps = create_acps.map_err(|e| {
admin_error!(err = ?e, "Unable to parse create accesscontrols");
e
})?;
self.accesscontrols
.update_create(create_acps)
.map_err(|e| {
admin_error!(err = ?e, "Failed to update create accesscontrols");
e
})?;
let filt = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
f_eq(Attribute::Class, EntryClass::AccessControlModify.into()),
f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
]));
let res = self.internal_search(filt).map_err(|e| {
admin_error!("reload accesscontrols internal search failed {:?}", e);
e
})?;
let modify_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlModify::try_from(self, e))
.collect();
let modify_acps = modify_acps.map_err(|e| {
admin_error!("Unable to parse modify accesscontrols {:?}", e);
e
})?;
self.accesscontrols
.update_modify(modify_acps)
.map_err(|e| {
admin_error!("Failed to update modify accesscontrols {:?}", e);
e
})?;
let filt = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::AccessControlProfile.into()),
f_eq(Attribute::Class, EntryClass::AccessControlDelete.into()),
f_andnot(f_eq(Attribute::AcpEnable, PV_FALSE.clone())),
]));
let res = self.internal_search(filt).map_err(|e| {
admin_error!("reload accesscontrols internal search failed {:?}", e);
e
})?;
let delete_acps: Result<Vec<_>, _> = res
.iter()
.map(|e| AccessControlDelete::try_from(self, e))
.collect();
let delete_acps = delete_acps.map_err(|e| {
admin_error!("Unable to parse delete accesscontrols {:?}", e);
e
})?;
self.accesscontrols.update_delete(delete_acps).map_err(|e| {
admin_error!("Failed to update delete accesscontrols {:?}", e);
e
})
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_key_material(&mut self) -> Result<(), OperationError> {
let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
let res = self.internal_search(filt).map_err(|e| {
admin_error!(
err = ?e,
"reload key providers internal search failed",
);
e
})?;
let providers = res
.iter()
.map(|e| KeyProvider::try_from(e).and_then(|kp| kp.test().map(|()| kp)))
.collect::<Result<Vec<_>, _>>()?;
self.key_providers.update_providers(providers)?;
let filt = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
let res = self.internal_search(filt).map_err(|e| {
admin_error!(
err = ?e,
"reload key objects internal search failed",
);
e
})?;
res.iter()
.try_for_each(|entry| self.key_providers.load_key_object(entry.as_ref()))
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
let denied_names = self.get_sc_denied_names()?;
let pw_badlist = self.get_sc_password_badlist()?;
let mut_system_config = self.system_config.get_mut();
mut_system_config.denied_names = denied_names;
mut_system_config.pw_badlist = pw_badlist;
Ok(())
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
let domain_info = self.internal_search_uuid(UUID_DOMAIN_INFO).map_err(|err| {
error!(?err, "Error getting domain info");
err
})?;
let domain_info_version = domain_info
.get_ava_single_uint32(Attribute::Version)
.ok_or_else(|| {
error!("domain info missing attribute version");
OperationError::InvalidEntryState
})?;
let domain_info_patch_level = domain_info
.get_ava_single_uint32(Attribute::PatchLevel)
.unwrap_or(0);
let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
let domain_info_devel_taint = current_devel_flag
|| domain_info
.get_ava_single_bool(Attribute::DomainDevelopmentTaint)
.unwrap_or_default();
let domain_allow_easter_eggs = domain_info
.get_ava_single_bool(Attribute::DomainAllowEasterEggs)
.unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some());
let mut_d_info = self.d_info.get_mut();
let previous_version = mut_d_info.d_vers;
let previous_patch_level = mut_d_info.d_patch_level;
mut_d_info.d_vers = domain_info_version;
mut_d_info.d_patch_level = domain_info_patch_level;
mut_d_info.d_devel_taint = domain_info_devel_taint;
mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs;
if (previous_version == domain_info_version
&& previous_patch_level == domain_info_patch_level)
|| *self.phase < ServerPhase::DomainInfoReady
{
return Ok(());
}
debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
if previous_version <= DOMAIN_LEVEL_5 && previous_version != DOMAIN_LEVEL_0 {
error!("UNABLE TO PROCEED. You are attempting a Skip update which is NOT SUPPORTED. You must upgrade one-version of Kanidm at a time.");
error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
error!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
return Err(OperationError::MG0008SkipUpgradeAttempted);
}
if previous_version <= DOMAIN_LEVEL_6 && domain_info_version >= DOMAIN_LEVEL_7 {
self.migrate_domain_6_to_7()?;
}
if previous_patch_level < PATCH_LEVEL_1 && domain_info_patch_level >= PATCH_LEVEL_1 {
self.migrate_domain_patch_level_1()?;
}
if previous_version <= DOMAIN_LEVEL_7 && domain_info_version >= DOMAIN_LEVEL_8 {
self.migrate_domain_7_to_8()?;
}
if previous_version <= DOMAIN_LEVEL_8 && domain_info_version >= DOMAIN_LEVEL_9 {
self.migrate_domain_8_to_9()?;
}
if previous_patch_level < PATCH_LEVEL_2 && domain_info_patch_level >= PATCH_LEVEL_2 {
self.migrate_domain_patch_level_2()?;
}
if previous_version <= DOMAIN_LEVEL_9 && domain_info_version >= DOMAIN_LEVEL_10 {
self.migrate_domain_9_to_10()?;
}
debug_assert!(domain_info_version <= DOMAIN_MAX_LEVEL);
Ok(())
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
let domain_entry = self.get_db_domain()?;
let domain_name = domain_entry
.get_ava_single_iname(Attribute::DomainName)
.map(str::to_string)
.ok_or(OperationError::InvalidEntryState)?;
let display_name = domain_entry
.get_ava_single_utf8(Attribute::DomainDisplayName)
.map(str::to_string)
.ok_or(OperationError::InvalidEntryState)?;
let domain_ldap_allow_unix_pw_bind = domain_entry
.get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
.unwrap_or(true);
let domain_image = domain_entry.get_ava_single_image(Attribute::Image);
let domain_uuid = self.be_txn.get_db_d_uuid()?;
let mut_d_info = self.d_info.get_mut();
mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind;
if mut_d_info.d_uuid != domain_uuid {
admin_warn!(
"Using domain uuid from the database {} - was {} in memory",
domain_name,
mut_d_info.d_name,
);
mut_d_info.d_uuid = domain_uuid;
}
if mut_d_info.d_name != domain_name {
admin_warn!(
"Using domain name from the database {} - was {} in memory",
domain_name,
mut_d_info.d_name,
);
admin_warn!(
"If you think this is an error, see https://kanidm.github.io/kanidm/master/domain_rename.html"
);
mut_d_info.d_name = domain_name;
}
mut_d_info.d_display = display_name;
mut_d_info.d_image = domain_image;
Ok(())
}
pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
let modl = ModifyList::new_purge_and_set(
Attribute::DomainDisplayName,
Value::new_utf8(new_domain_name.to_string()),
);
let udi = PVUUID_DOMAIN_INFO.clone();
let filt = filter_all!(f_eq(Attribute::Uuid, udi));
self.internal_modify(&filt, &modl)
}
pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> {
let modl =
ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name));
let udi = PVUUID_DOMAIN_INFO.clone();
let filt = filter_all!(f_eq(Attribute::Uuid, udi));
self.internal_modify(&filt, &modl)
}
pub fn reindex(&mut self, immediate: bool) -> Result<(), OperationError> {
self.be_txn.reindex(immediate)
}
fn force_schema_reload(&mut self) {
self.changed_flags.insert(ChangeFlag::SCHEMA);
}
fn force_domain_reload(&mut self) {
self.changed_flags.insert(ChangeFlag::DOMAIN);
}
pub(crate) fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> {
self.be_txn.upgrade_reindex(v)
}
#[inline]
pub(crate) fn get_changed_app(&self) -> bool {
self.changed_flags.contains(ChangeFlag::APPLICATION)
}
#[inline]
pub(crate) fn get_changed_oauth2(&self) -> bool {
self.changed_flags.contains(ChangeFlag::OAUTH2)
}
#[inline]
pub(crate) fn clear_changed_oauth2(&mut self) {
self.changed_flags.remove(ChangeFlag::OAUTH2)
}
fn set_phase(&mut self, phase: ServerPhase) {
*self.phase = phase
}
pub(crate) fn get_phase(&mut self) -> ServerPhase {
*self.phase
}
pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
if self.changed_flags.contains(ChangeFlag::DOMAIN) {
self.reload_domain_info_version()?;
}
if self.changed_flags.contains(ChangeFlag::SCHEMA) {
self.reload_schema()?;
if *self.phase >= ServerPhase::Running {
self.reindex(false)?;
self.reload_schema()?;
}
}
if self
.changed_flags
.intersects(ChangeFlag::SCHEMA | ChangeFlag::KEY_MATERIAL)
{
self.reload_key_material()?;
}
if self
.changed_flags
.intersects(ChangeFlag::SCHEMA | ChangeFlag::ACP | ChangeFlag::SYNC_AGREEMENT)
{
self.reload_accesscontrols()?;
} else {
}
if self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG) {
self.reload_system_config()?;
}
if self.changed_flags.contains(ChangeFlag::DOMAIN) {
self.reload_domain_info()?;
}
self.changed_flags.remove(
ChangeFlag::DOMAIN
| ChangeFlag::SCHEMA
| ChangeFlag::SYSTEM_CONFIG
| ChangeFlag::ACP
| ChangeFlag::SYNC_AGREEMENT
| ChangeFlag::KEY_MATERIAL,
);
Ok(())
}
#[cfg(any(test, debug_assertions))]
#[instrument(level = "debug", skip_all)]
pub fn clear_cache(&mut self) -> Result<(), OperationError> {
self.be_txn.clear_cache()
}
#[instrument(level = "info", name="qswt_commit" skip_all)]
pub fn commit(mut self) -> Result<(), OperationError> {
self.reload()?;
let QueryServerWriteTransaction {
committed,
phase,
d_info,
system_config,
mut be_txn,
schema,
accesscontrols,
cid,
dyngroup_cache,
key_providers,
_db_ticket,
_write_ticket,
curtime: _,
trim_cid: _,
changed_flags,
changed_uuid: _,
resolve_filter_cache: _,
} = self;
debug_assert!(!committed);
trace!(
changed = ?changed_flags.iter_names().collect::<Vec<_>>(),
);
be_txn.set_db_ts_max(cid.ts)?;
cid.commit();
schema
.commit()
.map(|_| d_info.commit())
.map(|_| system_config.commit())
.map(|_| phase.commit())
.map(|_| dyngroup_cache.commit())
.and_then(|_| key_providers.commit())
.and_then(|_| accesscontrols.commit())
.and_then(|_| be_txn.commit())
}
pub(crate) fn get_txn_cid(&self) -> &Cid {
&self.cid
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use kanidm_proto::scim_v1::server::ScimReference;
#[qs_test]
async fn test_name_to_uuid(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let t_uuid = Uuid::new_v4();
assert!(server_txn
.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::Uuid, Value::Uuid(t_uuid)),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
),])
.is_ok());
let r1 = server_txn.name_to_uuid("testpers");
assert!(r1.is_err());
let r2 = server_txn.name_to_uuid("tEsTpErS");
assert!(r2.is_err());
let r3 = server_txn.name_to_uuid("testperson1");
assert_eq!(r3, Ok(t_uuid));
let r4 = server_txn.name_to_uuid("tEsTpErSoN1");
assert_eq!(r4, Ok(t_uuid));
let r5 = server_txn.name_to_uuid("name=testperson1");
assert_eq!(r5, Ok(t_uuid));
let r6 = server_txn.name_to_uuid("name=testperson1,o=example");
assert_eq!(r6, Ok(t_uuid));
}
#[qs_test]
async fn test_external_id_to_uuid(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let t_uuid = Uuid::new_v4();
assert!(server_txn
.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::ExtensibleObject.to_value()),
(Attribute::Uuid, Value::Uuid(t_uuid)),
(
Attribute::SyncExternalId,
Value::new_iutf8("uid=testperson")
)
),])
.is_ok());
let r1 = server_txn.sync_external_id_to_uuid("tobias");
assert_eq!(r1, Ok(None));
let r2 = server_txn.sync_external_id_to_uuid("tObIAs");
assert_eq!(r2, Ok(None));
let r3 = server_txn.sync_external_id_to_uuid("uid=testperson");
assert_eq!(r3, Ok(Some(t_uuid)));
let r4 = server_txn.sync_external_id_to_uuid("uId=TeStPeRsOn");
assert_eq!(r4, Ok(Some(t_uuid)));
}
#[qs_test]
async fn test_uuid_to_spn(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let r1 = server_txn.uuid_to_spn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
assert_eq!(r1, Ok(None));
let r3 = server_txn.uuid_to_spn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
println!("{r3:?}");
assert_eq!(
r3.unwrap().unwrap(),
Value::new_spn_str("testperson1", "example.com")
);
let r4 = server_txn.uuid_to_spn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
assert_eq!(
r4.unwrap().unwrap(),
Value::new_spn_str("testperson1", "example.com")
);
}
#[qs_test]
async fn test_uuid_to_rdn(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let r1 = server_txn.uuid_to_rdn(uuid!("bae3f507-e6c3-44ba-ad01-f8ff1083534a"));
assert_eq!(r1.unwrap(), "uuid=bae3f507-e6c3-44ba-ad01-f8ff1083534a");
let r3 = server_txn.uuid_to_rdn(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"));
println!("{r3:?}");
assert_eq!(r3.unwrap(), "spn=testperson1@example.com");
let r4 = server_txn.uuid_to_rdn(uuid!("CC8E95B4-C24F-4D68-BA54-8BED76F63930"));
assert_eq!(r4.unwrap(), "spn=testperson1@example.com");
}
#[qs_test]
async fn test_clone_value(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("testperson1"))
);
let ce = CreateEvent::new_internal(vec![e1]);
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let r1 = server_txn.clone_value(&Attribute::from("tausau"), "naoeutnhaou");
assert!(r1.is_err());
let r2 = server_txn.clone_value(&Attribute::Custom("NaMe".into()), "NaMe");
assert!(r2.is_err());
let r3 = server_txn.clone_value(&Attribute::from("member"), "testperson1");
assert_eq!(
r3,
Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
);
let r4 = server_txn.clone_value(
&Attribute::from("member"),
"cc8e95b4-c24f-4d68-ba54-8bed76f63930",
);
debug!("{:?}", r4);
assert_eq!(
r4,
Ok(Value::Refer(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")))
);
}
#[qs_test]
async fn test_dynamic_schema_class(server: &QueryServer) {
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::TestClass.to_value()),
(Attribute::Name, Value::new_iname("testobj1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
)
);
let e_cd = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::ClassType.to_value()),
(Attribute::ClassName, EntryClass::TestClass.to_value()),
(
Attribute::Uuid,
Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
),
(Attribute::Description, Value::new_utf8s("Test Class")),
(Attribute::May, Value::from(Attribute::Name))
);
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce_class = CreateEvent::new_internal(vec![e_cd.clone()]);
assert!(server_txn.create(&ce_class).is_ok());
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(&ce_fail).is_err());
server_txn.commit().expect("should not fail");
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(&ce_work).is_ok());
server_txn.commit().expect("should not fail");
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let de_class = DeleteEvent::new_internal_invalid(filter!(f_eq(
Attribute::ClassName,
EntryClass::TestClass.into()
)));
assert!(server_txn.delete(&de_class).is_ok());
server_txn.commit().expect("should not fail");
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(&ce_fail).is_err());
let testobj1 = server_txn
.internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
.expect("failed");
assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into()));
server_txn.commit().expect("should not fail");
}
#[qs_test]
async fn test_dynamic_schema_attr(server: &QueryServer) {
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::ExtensibleObject.to_value()),
(Attribute::Name, Value::new_iname("testobj1")),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::TestAttr, Value::new_utf8s("test"))
);
let e_ad = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::AttributeType.to_value()),
(
Attribute::Uuid,
Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
),
(Attribute::AttributeName, Value::from(Attribute::TestAttr)),
(Attribute::Description, Value::new_utf8s("Test Attribute")),
(Attribute::MultiValue, Value::new_bool(false)),
(Attribute::Unique, Value::new_bool(false)),
(
Attribute::Syntax,
Value::new_syntaxs("UTF8STRING").expect("syntax")
)
);
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce_attr = CreateEvent::new_internal(vec![e_ad.clone()]);
assert!(server_txn.create(&ce_attr).is_ok());
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(&ce_fail).is_err());
server_txn.commit().expect("should not fail");
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce_work = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(&ce_work).is_ok());
server_txn.commit().expect("should not fail");
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
Attribute::AttributeName,
PartialValue::from(Attribute::TestAttr)
)));
assert!(server_txn.delete(&de_attr).is_ok());
server_txn.commit().expect("should not fail");
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let ce_fail = CreateEvent::new_internal(vec![e1.clone()]);
assert!(server_txn.create(&ce_fail).is_err());
let filt = filter!(f_eq(Attribute::TestAttr, PartialValue::new_utf8s("test")));
assert!(server_txn.internal_search(filt).is_err());
let testobj1 = server_txn
.internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
.expect("failed");
assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test")));
server_txn.commit().expect("should not fail");
}
#[qs_test]
async fn test_scim_entry_structure(server: &QueryServer) {
let mut read_txn = server.read().await.unwrap();
let entry = read_txn
.internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
.unwrap();
let reduced = entry.as_ref().clone().into_reduced();
let scim_entry = reduced.to_scim_kanidm(&mut read_txn).unwrap();
assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE);
let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap();
match name_scim {
ScimValueKanidm::String(name) => {
assert_eq!(name.clone(), "idm_people_self_name_write")
}
_ => {
panic!("expected String, actual {:?}", name_scim);
}
}
let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap();
match entry_managed_by_scim {
ScimValueKanidm::EntryReferences(managed_by) => {
assert_eq!(
managed_by.first().unwrap().clone(),
ScimReference {
uuid: UUID_IDM_ADMINS,
value: "idm_admins@example.com".to_string()
}
)
}
_ => {
panic!(
"expected EntryReference, actual {:?}",
entry_managed_by_scim
);
}
}
let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap();
match members_scim {
ScimValueKanidm::EntryReferences(members) => {
assert_eq!(
members.first().unwrap().clone(),
ScimReference {
uuid: UUID_IDM_ALL_PERSONS,
value: "idm_all_persons@example.com".to_string()
}
)
}
_ => {
panic!("expected EntryReferences, actual {:?}", members_scim);
}
}
}
}