use crate::prelude::*;
use kanidm_proto::internal::{
DomainUpgradeCheckItem as ProtoDomainUpgradeCheckItem,
DomainUpgradeCheckReport as ProtoDomainUpgradeCheckReport,
DomainUpgradeCheckStatus as ProtoDomainUpgradeCheckStatus,
};
use super::ServerPhase;
impl QueryServer {
#[instrument(level = "info", name = "system_initialisation", skip_all)]
pub async fn initialise_helper(
&self,
ts: Duration,
domain_target_level: DomainVersion,
) -> Result<(), OperationError> {
let mut write_txn = self.write(ts).await?;
write_txn.upgrade_reindex(SYSTEM_INDEX_VERSION)?;
write_txn
.initialise_schema_core()
.and_then(|_| write_txn.reload())?;
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)),
Err(OperationError::NoMatchingEntries) => Ok(0),
Err(r) => Err(r),
}?;
debug!(?db_domain_version, "Before setting internal domain info");
if db_domain_version == 0 {
write_txn.initialise_schema_idm()?;
write_txn.reload()?;
write_txn.reindex(false)?;
} else {
write_txn.force_domain_reload();
write_txn.reload()?;
}
write_txn.set_phase(ServerPhase::SchemaReady);
if db_domain_version == 0 {
write_txn.initialise_domain_info()?;
write_txn.initialise_idm()?;
} else {
write_txn.force_schema_reload();
};
write_txn.reload()?;
write_txn.set_phase(ServerPhase::DomainInfoReady);
let domain_info_version = write_txn.get_domain_version();
let domain_patch_level = write_txn.get_domain_patch_level();
let domain_development_taint = write_txn.get_domain_development_taint();
debug!(
?db_domain_version,
?domain_patch_level,
?domain_development_taint,
"After setting internal domain info"
);
let mut reload_required = false;
if domain_info_version < domain_target_level {
write_txn
.internal_modify_uuid(
UUID_DOMAIN_INFO,
&ModifyList::new_purge_and_set(
Attribute::Version,
Value::new_uint32(domain_target_level),
),
)
.map(|()| {
warn!("Domain level has been raised to {}", domain_target_level);
})?;
reload_required = true;
} else if domain_development_taint {
write_txn.domain_remigrate(DOMAIN_PREVIOUS_TGT_LEVEL)?;
reload_required = true;
}
if domain_target_level >= DOMAIN_LEVEL_7 && domain_patch_level < DOMAIN_TGT_PATCH_LEVEL {
write_txn
.internal_modify_uuid(
UUID_DOMAIN_INFO,
&ModifyList::new_purge_and_set(
Attribute::PatchLevel,
Value::new_uint32(DOMAIN_TGT_PATCH_LEVEL),
),
)
.map(|()| {
warn!(
"Domain patch level has been raised to {}",
domain_patch_level
);
})?;
reload_required = true;
};
if reload_required {
write_txn.reload()?;
write_txn.reindex(false)?;
}
let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
if current_devel_flag {
warn!("Domain Development Taint mode is enabled");
}
if domain_development_taint != current_devel_flag {
write_txn.internal_modify_uuid(
UUID_DOMAIN_INFO,
&ModifyList::new_purge_and_set(
Attribute::DomainDevelopmentTaint,
Value::Bool(current_devel_flag),
),
)?;
}
write_txn.set_phase(ServerPhase::Running);
write_txn.commit()?;
debug!("Database version check and migrations success! ☀️ ");
Ok(())
}
}
impl QueryServerWriteTransaction<'_> {
#[cfg(test)]
pub(crate) fn internal_apply_domain_migration(
&mut self,
to_level: u32,
) -> Result<(), OperationError> {
assert!(to_level > self.get_domain_version());
self.internal_modify_uuid(
UUID_DOMAIN_INFO,
&ModifyList::new_purge_and_set(Attribute::Version, Value::new_uint32(to_level)),
)
.and_then(|()| self.reload())
}
#[instrument(level = "debug", skip_all)]
pub fn internal_migrate_or_create(
&mut self,
e: Entry<EntryInit, EntryNew>,
) -> Result<(), OperationError> {
self.internal_migrate_or_create_ignore_attrs(e, &[])
}
#[instrument(level = "trace", skip_all)]
pub fn internal_migrate_or_create_ignore_attrs(
&mut self,
mut e: Entry<EntryInit, EntryNew>,
attrs: &[Attribute],
) -> Result<(), OperationError> {
trace!("operating on {:?}", e.get_uuid());
let Some(filt) = e.filter_from_attrs(&[Attribute::Uuid]) else {
return Err(OperationError::FilterGeneration);
};
trace!("search {:?}", filt);
let results = self.internal_search(filt.clone())?;
if results.is_empty() {
self.internal_create(vec![e])
} else if results.len() == 1 {
for attr in attrs.iter() {
e.remove_ava(attr);
}
match e.gen_modlist_assert(&self.schema) {
Ok(modlist) => {
trace!(?modlist);
self.internal_modify(&filt, &modlist)
}
Err(e) => Err(OperationError::SchemaViolation(e)),
}
} else {
admin_error!(
"Invalid Result Set - Expected One Entry for {:?} - {:?}",
filt,
results
);
Err(OperationError::InvalidDbState)
}
}
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_6_to_7(&mut self) -> Result<(), OperationError> {
if !cfg!(test) && DOMAIN_MAX_LEVEL < DOMAIN_LEVEL_7 {
error!("Unable to raise domain level from 6 to 7.");
return Err(OperationError::MG0004DomainLevelInDevelopment);
}
let filter = filter!(f_and!([
f_or!([
f_eq(Attribute::Class, EntryClass::PosixAccount.into()),
f_eq(Attribute::Class, EntryClass::PosixGroup.into())
]),
f_andnot(f_or!([
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_REGULAR_USER_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_REGULAR_USER_MIN)
))
]),
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_A_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_A_MIN)
))
]),
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_B_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_B_MIN)
))
]),
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_D_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_C_MIN)
))
]),
]))
]));
let results = self.internal_search(filter).map_err(|err| {
error!(?err, "migrate_domain_6_to_7 -> Error");
err
})?;
if !results.is_empty() {
error!("Unable to proceed. Not all entries meet gid/uid constraints.");
for entry in results {
error!(gid_invalid = ?entry.get_display_id());
}
return Err(OperationError::MG0005GidConstraintsNotMet);
}
let filter = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
f_pres(Attribute::OAuth2RsOrigin),
f_andnot(f_pres(Attribute::OAuth2RsOriginLanding)),
]));
let pre_candidates = self.internal_search(filter).map_err(|err| {
error!(?err, "migrate_domain_6_to_7 internal search failure");
err
})?;
let modset: Vec<_> = pre_candidates
.into_iter()
.filter_map(|ent| {
ent.get_ava_single_url(Attribute::OAuth2RsOrigin)
.map(|origin_url| {
let modlist = vec![Modify::Present(
Attribute::OAuth2RsOriginLanding,
Value::Url(origin_url.clone()),
)];
(ent.get_uuid(), ModifyList::new_list(modlist))
})
})
.collect();
if !modset.is_empty() {
self.internal_batch_modify(modset.into_iter())?;
}
let modlist = ModifyList::new_list(vec![
Modify::Purged(Attribute::PrivateCookieKey),
Modify::Purged(Attribute::Es256PrivateKeyDer),
Modify::Purged(Attribute::FernetPrivateKeyStr),
]);
self.internal_modify_uuid(UUID_DOMAIN_INFO, &modlist)?;
let filter = filter!(f_or!([
f_eq(Attribute::Class, EntryClass::ServiceAccount.into()),
f_eq(Attribute::Class, EntryClass::SyncAccount.into())
]));
let modlist = ModifyList::new_list(vec![Modify::Purged(Attribute::JwsEs256PrivateKey)]);
self.internal_modify(&filter, &modlist)?;
let idm_schema_classes = [
SCHEMA_ATTR_PATCH_LEVEL_DL7.clone().into(),
SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7.clone().into(),
SCHEMA_ATTR_REFERS_DL7.clone().into(),
SCHEMA_ATTR_CERTIFICATE_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7.clone().into(),
SCHEMA_ATTR_MAIL_DL7.clone().into(),
SCHEMA_ATTR_LEGALNAME_DL7.clone().into(),
SCHEMA_ATTR_DISPLAYNAME_DL7.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL7.clone().into(),
SCHEMA_CLASS_SERVICE_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_CLIENT_CERTIFICATE_DL7.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_DL7.clone().into(),
];
idm_schema_classes
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_6_to_7 -> Error");
err
})?;
self.reload()?;
let idm_data = [
BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7
.clone()
.try_into()?,
IDM_PEOPLE_SELF_MAIL_WRITE_DL7.clone().try_into()?,
BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7
.clone()
.try_into()?,
IDM_HIGH_PRIVILEGE_DL7.clone().try_into()?,
];
idm_data
.into_iter()
.try_for_each(|entry| {
self.internal_migrate_or_create_ignore_attrs(entry, &[Attribute::Member])
})
.map_err(|err| {
error!(?err, "migrate_domain_6_to_7 -> Error");
err
})?;
let idm_data = [
IDM_ACP_SELF_WRITE_DL7.clone().into(),
IDM_ACP_SELF_NAME_WRITE_DL7.clone().into(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone().into(),
IDM_ACP_OAUTH2_MANAGE_DL7.clone().into(),
];
idm_data
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_6_to_7 -> Error");
err
})?;
Ok(())
}
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_patch_level_1(&mut self) -> Result<(), OperationError> {
admin_warn!("applying domain patch 1.");
debug_assert!(*self.phase >= ServerPhase::SchemaReady);
let filter = filter!(f_eq(Attribute::Class, EntryClass::DynGroup.into()));
let modlist = modlist!([m_pres(Attribute::Class, &EntryClass::DynGroup.into())]);
self.internal_modify(&filter, &modlist).map(|()| {
info!("forced dyngroups to re-calculate memberships");
})
}
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_7_to_8(&mut self) -> Result<(), OperationError> {
if !cfg!(test) && DOMAIN_MAX_LEVEL < DOMAIN_LEVEL_8 {
error!("Unable to raise domain level from 7 to 8.");
return Err(OperationError::MG0004DomainLevelInDevelopment);
}
let filter = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::Account.into()),
f_pres(Attribute::PrimaryCredential),
]));
let results = self.internal_search(filter)?;
let affected_entries = results
.into_iter()
.filter_map(|entry| {
if entry
.get_ava_single_credential(Attribute::PrimaryCredential)
.map(|cred| cred.has_securitykey())
.unwrap_or_default()
{
Some(entry.get_display_id())
} else {
None
}
})
.collect::<Vec<_>>();
if !affected_entries.is_empty() {
error!("Unable to proceed. Some accounts still use legacy security keys, which need to be removed.");
for sk_present in affected_entries {
error!(%sk_present);
}
return Err(OperationError::MG0006SKConstraintsNotMet);
}
let filter = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
f_andnot(f_pres(Attribute::OAuth2StrictRedirectUri)),
]));
let results = self.internal_search(filter)?;
let affected_entries = results
.into_iter()
.map(|entry| entry.get_display_id())
.collect::<Vec<_>>();
if !affected_entries.is_empty() {
error!("Unable to proceed. Not all oauth2 clients have strict redirect verification enabled.");
for missing_oauth2_strict_redirect_uri in affected_entries {
error!(%missing_oauth2_strict_redirect_uri);
}
return Err(OperationError::MG0007Oauth2StrictConstraintsNotMet);
}
let idm_schema_classes = [
SCHEMA_ATTR_LINKED_GROUP_DL8.clone().into(),
SCHEMA_ATTR_APPLICATION_PASSWORD_DL8.clone().into(),
SCHEMA_CLASS_APPLICATION_DL8.clone().into(),
SCHEMA_CLASS_PERSON_DL8.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL8.clone().into(),
SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8.clone().into(),
SCHEMA_CLASS_ACCOUNT_POLICY_DL8.clone().into(),
];
idm_schema_classes
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_6_to_7 -> Error");
err
})?;
self.reload()?;
let idm_data = [
BUILTIN_GROUP_APPLICATION_ADMINS.clone().try_into()?,
IDM_ACP_SELF_READ_DL8.clone().into(),
IDM_ACP_SELF_WRITE_DL8.clone().into(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone().into(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone().into(),
BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8.clone().try_into()?,
BUILTIN_IDM_MAIL_SERVERS_DL8.clone().try_into()?,
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL8.clone().into(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
];
idm_data
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_7_to_8 -> Error");
err
})?;
Ok(())
}
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_8_to_9(&mut self) -> Result<(), OperationError> {
if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
error!("Unable to raise domain level from 8 to 9.");
return Err(OperationError::MG0004DomainLevelInDevelopment);
}
let idm_schema_changes = [
SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9.clone().into(),
SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS_DL9.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_DL9.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL9.clone().into(),
];
idm_schema_changes
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_8_to_9 -> Error");
err
})?;
self.reload()?;
let idm_data = [
IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(),
IDM_ACP_GROUP_MANAGE_DL9.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(),
];
idm_data
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_8_to_9 -> Error");
err
})?;
self.reload()?;
Ok(())
}
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_patch_level_2(&mut self) -> Result<(), OperationError> {
admin_warn!("applying domain patch 2.");
debug_assert!(*self.phase >= ServerPhase::SchemaReady);
let idm_data = [
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone().into(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone().into(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone().into(),
IDM_ACP_ACP_MANAGE_V1.clone().into(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone().into(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone().into(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL8.clone().into(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_GROUP_MANAGE_DL6.clone().into(),
IDM_ACP_GROUP_READ_V1.clone().into(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone().into(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
IDM_ACP_OAUTH2_MANAGE_DL7.clone().into(),
IDM_ACP_PEOPLE_CREATE_DL6.clone().into(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_PEOPLE_DELETE_V1.clone().into(),
IDM_ACP_PEOPLE_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_PII_READ_V1.clone().into(),
IDM_ACP_PEOPLE_READ_V1.clone().into(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone().into(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone().into(),
IDM_ACP_RADIUS_SERVERS_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone().into(),
IDM_ACP_SELF_NAME_WRITE_DL7.clone().into(),
IDM_ACP_SELF_READ_DL8.clone().into(),
IDM_ACP_SELF_WRITE_DL8.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1
.clone()
.into(),
];
idm_data
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_patch_level_2 -> Error");
err
})?;
self.reload()?;
Ok(())
}
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_9_to_10(&mut self) -> Result<(), OperationError> {
if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
error!("Unable to raise domain level from 8 to 9.");
return Err(OperationError::MG0004DomainLevelInDevelopment);
}
Ok(())
}
#[instrument(level = "info", skip_all)]
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
admin_debug!("initialise_schema_core -> start ...");
let entries = self.schema.to_entries();
let r: Result<_, _> = entries.into_iter().try_for_each(|e| {
trace!(?e, "init schema entry");
self.internal_migrate_or_create(e)
});
if r.is_ok() {
admin_debug!("initialise_schema_core -> Ok!");
} else {
admin_error!(?r, "initialise_schema_core -> Error");
}
debug_assert!(r.is_ok());
r
}
#[instrument(level = "info", skip_all)]
pub fn initialise_schema_idm(&mut self) -> Result<(), OperationError> {
admin_debug!("initialise_schema_idm -> start ...");
let idm_schema_attrs = [
SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL.clone().into(),
SCHEMA_ATTR_SYNC_YIELD_AUTHORITY.clone().into(),
];
let r: Result<(), _> = idm_schema_attrs
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry));
if r.is_err() {
error!(res = ?r, "initialise_schema_idm -> Error");
}
debug_assert!(r.is_ok());
let idm_schema: Vec<EntryInitNew> = vec![
SCHEMA_ATTR_MAIL.clone().into(),
SCHEMA_ATTR_ACCOUNT_EXPIRE.clone().into(),
SCHEMA_ATTR_ACCOUNT_VALID_FROM.clone().into(),
SCHEMA_ATTR_API_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_AUTH_SESSION_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH.clone().into(),
SCHEMA_ATTR_BADLIST_PASSWORD.clone().into(),
SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN.clone().into(),
SCHEMA_ATTR_ATTESTED_PASSKEYS.clone().into(),
SCHEMA_ATTR_DISPLAYNAME.clone().into(),
SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(),
SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(),
SCHEMA_ATTR_DOMAIN_NAME.clone().into(),
SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND.clone().into(),
SCHEMA_ATTR_DOMAIN_SSID.clone().into(),
SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_DOMAIN_UUID.clone().into(),
SCHEMA_ATTR_DYNGROUP_FILTER.clone().into(),
SCHEMA_ATTR_EC_KEY_PRIVATE.clone().into(),
SCHEMA_ATTR_ES256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_FERNET_PRIVATE_KEY_STR.clone().into(),
SCHEMA_ATTR_GIDNUMBER.clone().into(),
SCHEMA_ATTR_GRANT_UI_HINT.clone().into(),
SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY.clone().into(),
SCHEMA_ATTR_LEGALNAME.clone().into(),
SCHEMA_ATTR_LOGINSHELL.clone().into(),
SCHEMA_ATTR_NAME_HISTORY.clone().into(),
SCHEMA_ATTR_NSUNIQUEID.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
.clone()
.into(),
SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.clone().into(),
SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_NAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_OAUTH2_SESSION.clone().into(),
SCHEMA_ATTR_PASSKEYS.clone().into(),
SCHEMA_ATTR_PRIMARY_CREDENTIAL.clone().into(),
SCHEMA_ATTR_PRIVATE_COOKIE_KEY.clone().into(),
SCHEMA_ATTR_RADIUS_SECRET.clone().into(),
SCHEMA_ATTR_RS256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_SSH_PUBLICKEY.clone().into(),
SCHEMA_ATTR_SYNC_COOKIE.clone().into(),
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_DENIED_NAME.clone().into(),
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4
.clone()
.into(),
SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6.clone().into(),
SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6.clone().into(),
SCHEMA_ATTR_KEY_INTERNAL_DATA_DL6.clone().into(),
SCHEMA_ATTR_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_ROTATE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_REVOKE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_ES256_DL6.clone().into(),
SCHEMA_ATTR_PATCH_LEVEL_DL7.clone().into(),
SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7.clone().into(),
];
let r = idm_schema
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry));
if r.is_err() {
error!(res = ?r, "initialise_schema_idm -> Error");
}
debug_assert!(r.is_ok());
let idm_schema_classes_dl1: Vec<EntryInitNew> = vec![
SCHEMA_CLASS_DYNGROUP.clone().into(),
SCHEMA_CLASS_ORGPERSON.clone().into(),
SCHEMA_CLASS_POSIXACCOUNT.clone().into(),
SCHEMA_CLASS_POSIXGROUP.clone().into(),
SCHEMA_CLASS_SYSTEM_CONFIG.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_PUBLIC_DL4.clone().into(),
SCHEMA_CLASS_PERSON_DL5.clone().into(),
SCHEMA_CLASS_ACCOUNT_DL5.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_DL5.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_BASIC_DL5.clone().into(),
SCHEMA_CLASS_ACCOUNT_POLICY_DL6.clone().into(),
SCHEMA_CLASS_SERVICE_ACCOUNT_DL6.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL6.clone().into(),
SCHEMA_CLASS_GROUP_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWT_ES256_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL6.clone().into(),
];
let r: Result<(), _> = idm_schema_classes_dl1
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry));
if r.is_err() {
error!(res = ?r, "initialise_schema_idm -> Error");
}
debug_assert!(r.is_ok());
debug!("initialise_schema_idm -> Ok!");
r
}
#[instrument(level = "info", skip_all)]
pub fn initialise_domain_info(&mut self) -> Result<(), OperationError> {
self.internal_migrate_or_create(E_KEY_PROVIDER_INTERNAL_DL6.clone())
.and_then(|_| self.reload())
.map_err(|err| {
error!(?err, "initialise_domain_info::E_KEY_PROVIDER_INTERNAL_DL6");
debug_assert!(false);
err
})?;
self.internal_migrate_or_create(E_SYSTEM_INFO_V1.clone())
.and_then(|_| self.internal_migrate_or_create(E_DOMAIN_INFO_DL6.clone()))
.and_then(|_| self.internal_migrate_or_create(E_SYSTEM_CONFIG_V1.clone()))
.map_err(|err| {
error!(?err, "initialise_domain_info");
debug_assert!(false);
err
})
}
#[instrument(level = "info", skip_all)]
pub fn initialise_idm(&mut self) -> Result<(), OperationError> {
let admin_entries: Vec<EntryInitNew> = idm_builtin_admin_entries()?;
let res: Result<(), _> = admin_entries
.into_iter()
.try_for_each(|ent| self.internal_migrate_or_create(ent));
if res.is_ok() {
debug!("initialise_idm p1 -> result Ok!");
} else {
error!(?res, "initialise_idm p1 -> result");
}
debug_assert!(res.is_ok());
res?;
let res: Result<(), _> = idm_builtin_non_admin_groups()
.into_iter()
.try_for_each(|e| self.internal_migrate_or_create(e.clone().try_into()?));
if res.is_ok() {
debug!("initialise_idm p2 -> result Ok!");
} else {
error!(?res, "initialise_idm p2 -> result");
}
debug_assert!(res.is_ok());
res?;
let idm_entries: Vec<BuiltinAcp> = vec![
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone(),
IDM_ACP_ACP_MANAGE_V1.clone(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone(),
IDM_ACP_RADIUS_SERVERS_V1.clone(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone(),
IDM_ACP_SELF_READ_V1.clone(),
IDM_ACP_SELF_WRITE_V1.clone(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone(),
IDM_ACP_SELF_NAME_WRITE_V1.clone(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1.clone(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone(),
IDM_ACP_GROUP_READ_V1.clone(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone(),
IDM_ACP_PEOPLE_PII_READ_V1.clone(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone(),
IDM_ACP_PEOPLE_READ_V1.clone(),
IDM_ACP_PEOPLE_MANAGE_V1.clone(),
IDM_ACP_PEOPLE_DELETE_V1.clone(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1.clone(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone(),
IDM_ACP_OAUTH2_MANAGE_DL5.clone(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL6.clone(),
IDM_ACP_PEOPLE_CREATE_DL6.clone(),
IDM_ACP_GROUP_MANAGE_DL6.clone(),
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone(),
IDM_ACP_DOMAIN_ADMIN_DL6.clone(),
];
let res: Result<(), _> = idm_entries
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry.into()));
if res.is_ok() {
admin_debug!("initialise_idm p3 -> result Ok!");
} else {
admin_error!(?res, "initialise_idm p3 -> result");
}
debug_assert!(res.is_ok());
res
}
}
impl QueryServerReadTransaction<'_> {
pub fn domain_upgrade_check(
&mut self,
) -> Result<ProtoDomainUpgradeCheckReport, OperationError> {
let d_info = &self.d_info;
let name = d_info.d_name.clone();
let uuid = d_info.d_uuid;
let current_level = d_info.d_vers;
let upgrade_level = DOMAIN_TGT_NEXT_LEVEL;
let mut report_items = Vec::with_capacity(1);
if current_level <= DOMAIN_LEVEL_6 && upgrade_level >= DOMAIN_LEVEL_7 {
let item = self
.domain_upgrade_check_6_to_7_gidnumber()
.map_err(|err| {
error!(
?err,
"Failed to perform domain upgrade check 6 to 7 - gidnumber"
);
err
})?;
report_items.push(item);
}
if current_level <= DOMAIN_LEVEL_7 && upgrade_level >= DOMAIN_LEVEL_8 {
let item = self
.domain_upgrade_check_7_to_8_security_keys()
.map_err(|err| {
error!(
?err,
"Failed to perform domain upgrade check 7 to 8 - security-keys"
);
err
})?;
report_items.push(item);
let item = self
.domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri()
.map_err(|err| {
error!(
?err,
"Failed to perform domain upgrade check 7 to 8 - oauth2-strict-redirect_uri"
);
err
})?;
report_items.push(item);
}
Ok(ProtoDomainUpgradeCheckReport {
name,
uuid,
current_level,
upgrade_level,
report_items,
})
}
pub(crate) fn domain_upgrade_check_6_to_7_gidnumber(
&mut self,
) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
let filter = filter!(f_and!([
f_or!([
f_eq(Attribute::Class, EntryClass::PosixAccount.into()),
f_eq(Attribute::Class, EntryClass::PosixGroup.into())
]),
f_andnot(f_or!([
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_REGULAR_USER_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_REGULAR_USER_MIN)
))
]),
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_A_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_A_MIN)
))
]),
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_B_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_B_MIN)
))
]),
f_and!([
f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_D_MAX)
),
f_andnot(f_lt(
Attribute::GidNumber,
PartialValue::Uint32(crate::plugins::gidnumber::GID_UNUSED_C_MIN)
))
]),
]))
]));
let results = self.internal_search(filter)?;
let affected_entries = results
.into_iter()
.map(|entry| entry.get_display_id())
.collect::<Vec<_>>();
let status = if affected_entries.is_empty() {
ProtoDomainUpgradeCheckStatus::Pass6To7Gidnumber
} else {
ProtoDomainUpgradeCheckStatus::Fail6To7Gidnumber
};
Ok(ProtoDomainUpgradeCheckItem {
status,
from_level: DOMAIN_LEVEL_6,
to_level: DOMAIN_LEVEL_7,
affected_entries,
})
}
pub(crate) fn domain_upgrade_check_7_to_8_security_keys(
&mut self,
) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
let filter = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::Account.into()),
f_pres(Attribute::PrimaryCredential),
]));
let results = self.internal_search(filter)?;
let affected_entries = results
.into_iter()
.filter_map(|entry| {
if entry
.get_ava_single_credential(Attribute::PrimaryCredential)
.map(|cred| cred.has_securitykey())
.unwrap_or_default()
{
Some(entry.get_display_id())
} else {
None
}
})
.collect::<Vec<_>>();
let status = if affected_entries.is_empty() {
ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys
} else {
ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys
};
Ok(ProtoDomainUpgradeCheckItem {
status,
from_level: DOMAIN_LEVEL_7,
to_level: DOMAIN_LEVEL_8,
affected_entries,
})
}
pub(crate) fn domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri(
&mut self,
) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
let filter = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
f_andnot(f_pres(Attribute::OAuth2StrictRedirectUri)),
]));
let results = self.internal_search(filter)?;
let affected_entries = results
.into_iter()
.map(|entry| entry.get_display_id())
.collect::<Vec<_>>();
let status = if affected_entries.is_empty() {
ProtoDomainUpgradeCheckStatus::Pass7To8Oauth2StrictRedirectUri
} else {
ProtoDomainUpgradeCheckStatus::Fail7To8Oauth2StrictRedirectUri
};
Ok(ProtoDomainUpgradeCheckItem {
status,
from_level: DOMAIN_LEVEL_7,
to_level: DOMAIN_LEVEL_8,
affected_entries,
})
}
}
#[cfg(test)]
mod tests {
use super::{ProtoDomainUpgradeCheckItem, ProtoDomainUpgradeCheckStatus};
use crate::prelude::*;
#[qs_test]
async fn test_init_idempotent_schema_core(server: &QueryServer) {
{
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
assert!(server_txn.initialise_schema_core().is_ok());
}
{
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
assert!(server_txn.initialise_schema_core().is_ok());
assert!(server_txn.initialise_schema_core().is_ok());
assert!(server_txn.commit().is_ok());
}
{
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
assert!(server_txn.initialise_schema_core().is_ok());
}
{
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
assert!(server_txn.initialise_schema_core().is_ok());
assert!(server_txn.commit().is_ok());
}
}
#[qs_test(domain_level=DOMAIN_LEVEL_6)]
async fn test_migrations_dl6_dl7(server: &QueryServer) {
let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
let db_domain_version = write_txn
.internal_search_uuid(UUID_DOMAIN_INFO)
.expect("unable to access domain entry")
.get_ava_single_uint32(Attribute::Version)
.expect("Attribute Version not present");
assert_eq!(db_domain_version, DOMAIN_LEVEL_6);
let oauth2_client_uuid = Uuid::new_v4();
let ea: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Uuid, Value::Uuid(oauth2_client_uuid)),
(
Attribute::Class,
EntryClass::OAuth2ResourceServer.to_value()
),
(
Attribute::Class,
EntryClass::OAuth2ResourceServerPublic.to_value()
),
(Attribute::Name, Value::new_iname("test_resource_server")),
(
Attribute::DisplayName,
Value::new_utf8s("test_resource_server")
),
(
Attribute::OAuth2RsOrigin,
Value::new_url_s("https://demo.example.com").unwrap()
)
);
write_txn
.internal_create(vec![ea])
.expect("Unable to create oauth2 client");
write_txn
.internal_apply_domain_migration(DOMAIN_LEVEL_7)
.expect("Unable to set domain level to version 7");
let domain_entry = write_txn
.internal_search_uuid(UUID_DOMAIN_INFO)
.expect("Unable to access domain entry");
assert!(!domain_entry.attribute_pres(Attribute::PrivateCookieKey));
let oauth2_entry = write_txn
.internal_search_uuid(oauth2_client_uuid)
.expect("Unable to access oauth2 client entry");
let origin = oauth2_entry
.get_ava_single_url(Attribute::OAuth2RsOrigin)
.expect("Unable to access oauth2 client origin");
let landing = oauth2_entry
.get_ava_single_url(Attribute::OAuth2RsOriginLanding)
.expect("Unable to access oauth2 client landing");
assert_eq!(origin, landing);
write_txn.commit().expect("Unable to commit");
}
#[qs_test(domain_level=DOMAIN_LEVEL_7)]
async fn test_migrations_dl7_dl8(server: &QueryServer) {
let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
let db_domain_version = write_txn
.internal_search_uuid(UUID_DOMAIN_INFO)
.expect("unable to access domain entry")
.get_ava_single_uint32(Attribute::Version)
.expect("Attribute Version not present");
assert_eq!(db_domain_version, DOMAIN_LEVEL_7);
let oauth2_client_uuid = Uuid::new_v4();
let ea: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Uuid, Value::Uuid(oauth2_client_uuid)),
(
Attribute::Class,
EntryClass::OAuth2ResourceServer.to_value()
),
(
Attribute::Class,
EntryClass::OAuth2ResourceServerPublic.to_value()
),
(Attribute::Name, Value::new_iname("test_resource_server")),
(
Attribute::DisplayName,
Value::new_utf8s("test_resource_server")
),
(
Attribute::OAuth2RsOriginLanding,
Value::new_url_s("https://demo.example.com/oauth2").unwrap()
),
(
Attribute::OAuth2RsOrigin,
Value::new_url_s("https://demo.example.com").unwrap()
)
);
write_txn
.internal_create(vec![ea])
.expect("Unable to create oauth2 client");
write_txn.commit().expect("Unable to commit");
let mut read_txn = server.read().await.unwrap();
match read_txn.domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri() {
Ok(ProtoDomainUpgradeCheckItem {
status: ProtoDomainUpgradeCheckStatus::Fail7To8Oauth2StrictRedirectUri,
..
}) => {
trace!("Failed as expected, very good.");
}
other => {
error!(?other);
unreachable!();
}
};
drop(read_txn);
let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
write_txn
.internal_modify_uuid(
oauth2_client_uuid,
&ModifyList::new_purge_and_set(
Attribute::OAuth2StrictRedirectUri,
Value::Bool(true),
),
)
.expect("Unable to enforce strict mode.");
write_txn
.internal_apply_domain_migration(DOMAIN_LEVEL_8)
.expect("Unable to set domain level to version 8");
write_txn.commit().expect("Unable to commit");
}
#[qs_test(domain_level=DOMAIN_LEVEL_8)]
async fn test_migrations_dl8_dl9(_server: &QueryServer) {}
#[qs_test(domain_level=DOMAIN_LEVEL_9)]
async fn test_migrations_dl9_dl10(_server: &QueryServer) {}
}