mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
Add improved domain migration framework and default MFA (#2382)
This commit is contained in:
parent
77b01e3a31
commit
fd71a748ca
|
@ -47,6 +47,7 @@ use kanidmd_lib::idm::ldap::LdapServer;
|
||||||
use kanidmd_lib::prelude::*;
|
use kanidmd_lib::prelude::*;
|
||||||
use kanidmd_lib::schema::Schema;
|
use kanidmd_lib::schema::Schema;
|
||||||
use kanidmd_lib::status::StatusActor;
|
use kanidmd_lib::status::StatusActor;
|
||||||
|
use kanidmd_lib::value::CredentialType;
|
||||||
#[cfg(not(target_family = "windows"))]
|
#[cfg(not(target_family = "windows"))]
|
||||||
use libc::umask;
|
use libc::umask;
|
||||||
|
|
||||||
|
@ -876,6 +877,7 @@ pub async fn create_server_core(
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add admin to idm_admins to allow tests more flexibility wrt to permissions.
|
// Add admin to idm_admins to allow tests more flexibility wrt to permissions.
|
||||||
// This way our default access controls can be stricter to prevent lateral
|
// This way our default access controls can be stricter to prevent lateral
|
||||||
// movement.
|
// movement.
|
||||||
|
@ -891,7 +893,25 @@ pub async fn create_server_core(
|
||||||
);
|
);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match idms_prox_write.qs_write.internal_modify_uuid(
|
||||||
|
UUID_IDM_ALL_PERSONS,
|
||||||
|
&ModifyList::new_purge_and_set(
|
||||||
|
Attribute::CredentialTypeMinimum,
|
||||||
|
CredentialType::Any.into(),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Unable to configure INTEGRATION TEST default credential policy -> {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(());
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match idms_prox_write.commit() {
|
match idms_prox_write.commit() {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -45,12 +45,13 @@ pub type DomainVersion = u32;
|
||||||
|
|
||||||
pub const DOMAIN_LEVEL_1: DomainVersion = 1;
|
pub const DOMAIN_LEVEL_1: DomainVersion = 1;
|
||||||
pub const DOMAIN_LEVEL_2: DomainVersion = 2;
|
pub const DOMAIN_LEVEL_2: DomainVersion = 2;
|
||||||
|
pub const DOMAIN_LEVEL_3: DomainVersion = 3;
|
||||||
// The minimum supported domain functional level
|
// The minimum supported domain functional level
|
||||||
pub const DOMAIN_MIN_LEVEL: DomainVersion = DOMAIN_LEVEL_2;
|
pub const DOMAIN_MIN_LEVEL: DomainVersion = DOMAIN_LEVEL_2;
|
||||||
// The target supported domain functional level
|
// The target supported domain functional level
|
||||||
pub const DOMAIN_TGT_LEVEL: DomainVersion = DOMAIN_LEVEL_2;
|
pub const DOMAIN_TGT_LEVEL: DomainVersion = DOMAIN_LEVEL_3;
|
||||||
// The maximum supported domain functional level
|
// The maximum supported domain functional level
|
||||||
pub const DOMAIN_MAX_LEVEL: DomainVersion = DOMAIN_LEVEL_2;
|
pub const DOMAIN_MAX_LEVEL: DomainVersion = DOMAIN_LEVEL_3;
|
||||||
|
|
||||||
// On test builds, define to 60 seconds
|
// On test builds, define to 60 seconds
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -660,7 +660,7 @@ impl Filter<FilterInvalid> {
|
||||||
// This has to have two versions to account for ro/rw traits, because RS can't
|
// This has to have two versions to account for ro/rw traits, because RS can't
|
||||||
// monomorphise on the trait to call clone_value. An option is to make a fn that
|
// monomorphise on the trait to call clone_value. An option is to make a fn that
|
||||||
// takes "clone_value(t, a, v) instead, but that may have a similar issue.
|
// takes "clone_value(t, a, v) instead, but that may have a similar issue.
|
||||||
#[instrument(name = "filter::from_ro", level = "debug", skip_all)]
|
#[instrument(name = "filter::from_ro", level = "trace", skip_all)]
|
||||||
pub fn from_ro(
|
pub fn from_ro(
|
||||||
ev: &Identity,
|
ev: &Identity,
|
||||||
f: &ProtoFilter,
|
f: &ProtoFilter,
|
||||||
|
@ -675,7 +675,7 @@ impl Filter<FilterInvalid> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(name = "filter::from_rw", level = "debug", skip_all)]
|
#[instrument(name = "filter::from_rw", level = "trace", skip_all)]
|
||||||
pub fn from_rw(
|
pub fn from_rw(
|
||||||
ev: &Identity,
|
ev: &Identity,
|
||||||
f: &ProtoFilter,
|
f: &ProtoFilter,
|
||||||
|
@ -690,7 +690,7 @@ impl Filter<FilterInvalid> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(name = "filter::from_ldap_ro", level = "debug", skip_all)]
|
#[instrument(name = "filter::from_ldap_ro", level = "trace", skip_all)]
|
||||||
pub fn from_ldap_ro(
|
pub fn from_ldap_ro(
|
||||||
ev: &Identity,
|
ev: &Identity,
|
||||||
f: &LdapFilter,
|
f: &LdapFilter,
|
||||||
|
|
|
@ -2448,6 +2448,13 @@ mod tests {
|
||||||
) -> (CredentialUpdateSessionToken, CredentialUpdateSessionStatus) {
|
) -> (CredentialUpdateSessionToken, CredentialUpdateSessionStatus) {
|
||||||
let mut idms_prox_write = idms.proxy_write(ct).await;
|
let mut idms_prox_write = idms.proxy_write(ct).await;
|
||||||
|
|
||||||
|
// Remove the default all persons policy, it interfers with our test.
|
||||||
|
let modlist = ModifyList::new_purge(Attribute::CredentialTypeMinimum);
|
||||||
|
idms_prox_write
|
||||||
|
.qs_write
|
||||||
|
.internal_modify_uuid(UUID_IDM_ALL_PERSONS, &modlist)
|
||||||
|
.expect("Unable to change default session exp");
|
||||||
|
|
||||||
let e2 = entry_init!(
|
let e2 = entry_init!(
|
||||||
(Attribute::Class, EntryClass::Object.to_value()),
|
(Attribute::Class, EntryClass::Object.to_value()),
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
|
|
|
@ -279,12 +279,10 @@ mod tests {
|
||||||
|
|
||||||
let pw = crate::utils::password_from_random();
|
let pw = crate::utils::password_from_random();
|
||||||
|
|
||||||
let c_status = cutxn
|
let _c_status = cutxn
|
||||||
.credential_primary_set_password(&cust, ct, &pw)
|
.credential_primary_set_password(&cust, ct, &pw)
|
||||||
.expect("Failed to update the primary cred password");
|
.expect("Failed to update the primary cred password");
|
||||||
|
|
||||||
assert!(c_status.can_commit());
|
|
||||||
|
|
||||||
let c_status = cutxn
|
let c_status = cutxn
|
||||||
.credential_primary_init_totp(&cust, ct)
|
.credential_primary_init_totp(&cust, ct)
|
||||||
.expect("Failed to update the primary cred password");
|
.expect("Failed to update the primary cred password");
|
||||||
|
|
|
@ -106,8 +106,10 @@ impl Domain {
|
||||||
if !e.attribute_pres(Attribute::Version) {
|
if !e.attribute_pres(Attribute::Version) {
|
||||||
let n = Value::Uint32(DOMAIN_MIN_LEVEL);
|
let n = Value::Uint32(DOMAIN_MIN_LEVEL);
|
||||||
e.set_ava(Attribute::Version, once(n));
|
e.set_ava(Attribute::Version, once(n));
|
||||||
trace!("plugin_domain: Applying domain version transform");
|
warn!("plugin_domain: Applying domain version transform");
|
||||||
}
|
} else {
|
||||||
|
warn!("plugin_domain: NOT Applying domain version transform");
|
||||||
|
};
|
||||||
|
|
||||||
// create the domain_display_name if it's missing
|
// create the domain_display_name if it's missing
|
||||||
if !e.attribute_pres(Attribute::DomainDisplayName) {
|
if !e.attribute_pres(Attribute::DomainDisplayName) {
|
||||||
|
|
|
@ -351,8 +351,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// This is re-loaded in case the domain name changed on the remote. Also needed for changing
|
// This is re-loaded in case the domain name changed on the remote
|
||||||
// the domain version.
|
|
||||||
self.reload_domain_info().map_err(|e| {
|
self.reload_domain_info().map_err(|e| {
|
||||||
error!("Failed to reload domain info");
|
error!("Failed to reload domain info");
|
||||||
e
|
e
|
||||||
|
@ -371,6 +370,17 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Reload the domain version, doing any needed migrations.
|
||||||
|
//
|
||||||
|
// While it seems odd that we do the migrations after we recieve the entries,
|
||||||
|
// this is because the supplier will already be sending us everything that
|
||||||
|
// was just migrated. As a result, we only need to apply the migrations to entries
|
||||||
|
// that were not on the supplier, and therefore need updates here.
|
||||||
|
self.reload_domain_info_version().map_err(|e| {
|
||||||
|
error!("Failed to reload domain info version");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
// Finally, confirm that the ranges that we have added match the ranges from our
|
// Finally, confirm that the ranges that we have added match the ranges from our
|
||||||
// context. Note that we get this in a writeable form!
|
// context. Note that we get this in a writeable form!
|
||||||
let ruv = self.be_txn.get_ruv_write();
|
let ruv = self.be_txn.get_ruv_write();
|
||||||
|
|
|
@ -164,7 +164,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>;
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>;
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "access::search_related_acp", skip_all)]
|
#[instrument(level = "trace", name = "access::search_related_acp", skip_all)]
|
||||||
fn search_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlSearchResolved<'b>> {
|
fn search_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlSearchResolved<'b>> {
|
||||||
let search_state = self.get_search();
|
let search_state = self.get_search();
|
||||||
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
|
let acp_resolve_filter_cache = self.get_acp_resolve_filter_cache();
|
||||||
|
@ -369,7 +369,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
Ok(allowed_entries)
|
Ok(allowed_entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "access::modify_related_acp", skip_all)]
|
#[instrument(level = "trace", name = "access::modify_related_acp", skip_all)]
|
||||||
fn modify_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlModifyResolved<'b>> {
|
fn modify_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlModifyResolved<'b>> {
|
||||||
// Some useful references we'll use for the remainder of the operation
|
// Some useful references we'll use for the remainder of the operation
|
||||||
let modify_state = self.get_modify();
|
let modify_state = self.get_modify();
|
||||||
|
@ -706,7 +706,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "access::delete_related_acp", skip_all)]
|
#[instrument(level = "trace", name = "access::delete_related_acp", skip_all)]
|
||||||
fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
|
fn delete_related_acp<'b>(&'b self, ident: &Identity) -> Vec<AccessControlDeleteResolved<'b>> {
|
||||||
// Some useful references we'll use for the remainder of the operation
|
// Some useful references we'll use for the remainder of the operation
|
||||||
let delete_state = self.get_delete();
|
let delete_state = self.get_delete();
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::value::CredentialType;
|
||||||
use kanidm_proto::v1::SchemaError;
|
use kanidm_proto::v1::SchemaError;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -121,16 +122,42 @@ impl QueryServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the start of domain info related migrations which we will need in future
|
// Reload if anything in migrations requires it.
|
||||||
// to handle replication. Due to the access control rework, and the addition of "managed by"
|
write_txn.reload()?;
|
||||||
// syntax, we need to ensure both node "fence" replication from each other. We do this
|
|
||||||
// by changing domain infos to be incompatible during this phase.
|
// This is what tells us if the domain entry existed before or not.
|
||||||
let domain_info_version = match write_txn.internal_search_uuid(UUID_DOMAIN_INFO) {
|
let db_domain_version = match write_txn.internal_search_uuid(UUID_DOMAIN_INFO) {
|
||||||
Ok(e) => Ok(e.get_ava_single_uint32(Attribute::Version).unwrap_or(0)),
|
Ok(e) => Ok(e.get_ava_single_uint32(Attribute::Version).unwrap_or(0)),
|
||||||
Err(OperationError::NoMatchingEntries) => Ok(0),
|
Err(OperationError::NoMatchingEntries) => Ok(0),
|
||||||
Err(r) => Err(r),
|
Err(r) => Err(r),
|
||||||
}?;
|
}?;
|
||||||
admin_debug!(?domain_info_version);
|
|
||||||
|
info!(?db_domain_version, "Before setting internal domain info");
|
||||||
|
|
||||||
|
// Migrations complete. Init idm will now set the system config version and minimum domain
|
||||||
|
// level if none was present
|
||||||
|
write_txn.initialise_domain_info()?;
|
||||||
|
|
||||||
|
// No domain info was present, so neither was the rest of the IDM.
|
||||||
|
if db_domain_version == 0 {
|
||||||
|
write_txn.initialise_idm()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now force everything to reload, init idm can touch a lot.
|
||||||
|
write_txn.force_all_reload();
|
||||||
|
write_txn.reload()?;
|
||||||
|
|
||||||
|
// Domain info is now ready and reloaded, we can proceed.
|
||||||
|
write_txn.set_phase(ServerPhase::DomainInfoReady);
|
||||||
|
|
||||||
|
// This is the start of domain info related migrations which we will need in future
|
||||||
|
// to handle replication. Due to the access control rework, and the addition of "managed by"
|
||||||
|
// syntax, we need to ensure both node "fence" replication from each other. We do this
|
||||||
|
// by changing domain infos to be incompatible during this phase.
|
||||||
|
|
||||||
|
// The reloads will have populated this structure now.
|
||||||
|
let domain_info_version = write_txn.get_domain_version();
|
||||||
|
info!(?db_domain_version, "After setting internal domain info");
|
||||||
|
|
||||||
if domain_info_version < DOMAIN_TGT_LEVEL {
|
if domain_info_version < DOMAIN_TGT_LEVEL {
|
||||||
write_txn
|
write_txn
|
||||||
|
@ -146,13 +173,9 @@ impl QueryServer {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload if anything in migrations requires it.
|
// Reload if anything in migrations requires it - this triggers the domain migrations.
|
||||||
write_txn.reload()?;
|
write_txn.reload()?;
|
||||||
// Migrations complete. Init idm will now set the version as needed.
|
|
||||||
write_txn.initialise_idm()?;
|
|
||||||
|
|
||||||
// Now force everything to reload.
|
|
||||||
write_txn.force_all_reload();
|
|
||||||
// We are ready to run
|
// We are ready to run
|
||||||
write_txn.set_phase(ServerPhase::Running);
|
write_txn.set_phase(ServerPhase::Running);
|
||||||
|
|
||||||
|
@ -643,6 +666,36 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
|
/// This migration will
|
||||||
|
/// * Trigger a "once off" mfa account policy rule on all persons.
|
||||||
|
pub fn migrate_domain_2_to_3(&mut self) -> Result<(), OperationError> {
|
||||||
|
let idm_all_persons = match self.internal_search_uuid(UUID_IDM_ALL_PERSONS) {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
Err(OperationError::NoMatchingEntries) => return Ok(()),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let credential_policy =
|
||||||
|
idm_all_persons.get_ava_single_credential_type(Attribute::CredentialTypeMinimum);
|
||||||
|
|
||||||
|
if credential_policy.is_some() {
|
||||||
|
debug!("Credential policy already present, not applying change.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal_modify_uuid(
|
||||||
|
UUID_IDM_ALL_PERSONS,
|
||||||
|
&ModifyList::new_purge_and_set(
|
||||||
|
Attribute::CredentialTypeMinimum,
|
||||||
|
CredentialType::Mfa.into(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|()| {
|
||||||
|
info!("Upgraded default account policy to enforce MFA");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
|
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
|
||||||
admin_debug!("initialise_schema_core -> start ...");
|
admin_debug!("initialise_schema_core -> start ...");
|
||||||
|
@ -790,7 +843,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
/// This function is idempotent, runs all the startup functionality and checks
|
/// This function is idempotent, runs all the startup functionality and checks
|
||||||
pub fn initialise_idm(&mut self) -> Result<(), OperationError> {
|
pub fn initialise_domain_info(&mut self) -> Result<(), OperationError> {
|
||||||
// First, check the system_info object. This stores some server information
|
// First, check the system_info object. This stores some server information
|
||||||
// and details. It's a pretty const thing. Also check anonymous, important to many
|
// and details. It's a pretty const thing. Also check anonymous, important to many
|
||||||
// concepts.
|
// concepts.
|
||||||
|
@ -802,8 +855,12 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
admin_error!("initialise_idm p1 -> result {:?}", res);
|
admin_error!("initialise_idm p1 -> result {:?}", res);
|
||||||
}
|
}
|
||||||
debug_assert!(res.is_ok());
|
debug_assert!(res.is_ok());
|
||||||
res?;
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
|
/// This function is idempotent, runs all the startup functionality and checks
|
||||||
|
pub fn initialise_idm(&mut self) -> Result<(), OperationError> {
|
||||||
// The domain info now exists, we should be able to do these migrations as they will
|
// The domain info now exists, we should be able to do these migrations as they will
|
||||||
// cause SPN regenerations to occur
|
// cause SPN regenerations to occur
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ pub type ResolveFilterCacheReadTxn<'a> =
|
||||||
enum ServerPhase {
|
enum ServerPhase {
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
SchemaReady,
|
SchemaReady,
|
||||||
|
DomainInfoReady,
|
||||||
Running,
|
Running,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +166,8 @@ pub trait QueryServerTransaction<'a> {
|
||||||
|
|
||||||
fn denied_names(&self) -> &HashSet<String>;
|
fn denied_names(&self) -> &HashSet<String>;
|
||||||
|
|
||||||
|
fn get_domain_version(&self) -> DomainVersion;
|
||||||
|
|
||||||
fn get_domain_uuid(&self) -> Uuid;
|
fn get_domain_uuid(&self) -> Uuid;
|
||||||
|
|
||||||
fn get_domain_name(&self) -> &str;
|
fn get_domain_name(&self) -> &str;
|
||||||
|
@ -989,6 +992,10 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
|
||||||
&self.system_config.denied_names
|
&self.system_config.denied_names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_domain_version(&self) -> DomainVersion {
|
||||||
|
self.d_info.d_vers
|
||||||
|
}
|
||||||
|
|
||||||
fn get_domain_uuid(&self) -> Uuid {
|
fn get_domain_uuid(&self) -> Uuid {
|
||||||
self.d_info.d_uuid
|
self.d_info.d_uuid
|
||||||
}
|
}
|
||||||
|
@ -1110,6 +1117,10 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
|
||||||
&self.system_config.denied_names
|
&self.system_config.denied_names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_domain_version(&self) -> DomainVersion {
|
||||||
|
self.d_info.d_vers
|
||||||
|
}
|
||||||
|
|
||||||
fn get_domain_uuid(&self) -> Uuid {
|
fn get_domain_uuid(&self) -> Uuid {
|
||||||
self.d_info.d_uuid
|
self.d_info.d_uuid
|
||||||
}
|
}
|
||||||
|
@ -1580,6 +1591,40 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pulls the domain name from the database and updates the DomainInfo data in memory
|
||||||
|
#[instrument(level = "debug", skip_all)]
|
||||||
|
pub(crate) fn reload_domain_info_version(&mut self) -> Result<(), OperationError> {
|
||||||
|
let domain_info_version = self
|
||||||
|
.internal_search_uuid(UUID_DOMAIN_INFO)
|
||||||
|
.and_then(|e| {
|
||||||
|
e.get_ava_single_uint32(Attribute::Version)
|
||||||
|
.ok_or(OperationError::InvalidEntryState)
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
error!(?err, "Error getting domain version");
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// We have to set the domain version here so that features which check for it
|
||||||
|
// will now see it's been increased.
|
||||||
|
let mut_d_info = self.d_info.get_mut();
|
||||||
|
let previous_version = mut_d_info.d_vers;
|
||||||
|
mut_d_info.d_vers = domain_info_version;
|
||||||
|
|
||||||
|
if previous_version == domain_info_version || *self.phase < ServerPhase::DomainInfoReady {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
|
||||||
|
|
||||||
|
if previous_version <= DOMAIN_LEVEL_2 && domain_info_version >= DOMAIN_LEVEL_3 {
|
||||||
|
// 2 -> 3 Migration
|
||||||
|
self.migrate_domain_2_to_3()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Pulls the domain name from the database and updates the DomainInfo data in memory
|
/// Pulls the domain name from the database and updates the DomainInfo data in memory
|
||||||
#[instrument(level = "debug", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
|
pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
|
||||||
|
@ -1689,6 +1734,11 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
|
pub(crate) fn reload(&mut self) -> Result<(), OperationError> {
|
||||||
|
// First, check if the domain version has changed.
|
||||||
|
if self.changed_domain {
|
||||||
|
self.reload_domain_info_version()?;
|
||||||
|
}
|
||||||
|
|
||||||
// This could be faster if we cache the set of classes changed
|
// This could be faster if we cache the set of classes changed
|
||||||
// in an operation so we can check if we need to do the reload or not
|
// in an operation so we can check if we need to do the reload or not
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use core::result::Result::Err;
|
use core::result::Result::Err;
|
||||||
use kanidm_client::KanidmClient;
|
use kanidm_client::KanidmClient;
|
||||||
use kanidm_proto::{
|
use kanidm_proto::internal::{IdentifyUserRequest, IdentifyUserResponse};
|
||||||
internal::{IdentifyUserRequest, IdentifyUserResponse},
|
|
||||||
v1::Entry,
|
|
||||||
};
|
|
||||||
|
|
||||||
use kanidmd_lib::prelude::Attribute;
|
use kanidmd_lib::prelude::Attribute;
|
||||||
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
|
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
|
||||||
|
@ -266,24 +263,15 @@ async fn setup_server(rsclient: &KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_user(rsclient: &KanidmClient, user: &str) -> String {
|
async fn create_user(rsclient: &KanidmClient, user: &str) -> String {
|
||||||
let e: Entry = serde_json::from_str(&format!(
|
rsclient
|
||||||
r#"{{
|
.idm_person_account_create(user, &format!("dx{}", user))
|
||||||
"attrs": {{
|
.await
|
||||||
"class": ["account", "person", "object"],
|
.expect("Unable to create person");
|
||||||
"name": ["{}"],
|
|
||||||
"displayname": ["dx{}"]
|
|
||||||
}}
|
|
||||||
}}"#,
|
|
||||||
user, user
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
let res = rsclient.create(vec![e.clone()]).await;
|
|
||||||
|
|
||||||
assert!(res.is_ok());
|
|
||||||
|
|
||||||
rsclient
|
rsclient
|
||||||
.idm_person_account_primary_credential_set_password(user, UNIVERSAL_PW)
|
.idm_person_account_primary_credential_set_password(user, UNIVERSAL_PW)
|
||||||
|
|
Loading…
Reference in a new issue