Schema-dooby-doo-part-trois (#2082)

* adding extra_attributes field to BuiltinGroup, migrating more things.
* checkpoint 3 - ACP, easy as 1,2,3
* codespell
* now throwing error on dyngroup with defined members
This commit is contained in:
James Hodgkinson 2023-09-09 09:38:47 +10:00 committed by GitHub
parent 4b7563adc8
commit d3d80e7364
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 627 additions and 710 deletions

View file

@ -1,6 +1,6 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![allow(non_snake_case)]
use num_enum::{IntoPrimitive, TryFromPrimitive};
use tracing_forest::util::*;
use tracing_forest::Tag;

View file

@ -7,7 +7,11 @@ use crate::prelude::*;
use crate::value::Value;
use kanidm_proto::v1::Filter as ProtoFilter;
#[derive(Clone)]
lazy_static! {
pub static ref DEFAULT_TARGET_SCOPE: ProtoFilter = ProtoFilter::And(Vec::with_capacity(0));
}
#[derive(Clone, Debug)]
/// Built-in Access Control Profile definitions
pub struct BuiltinAcp {
classes: Vec<EntryClass>,
@ -17,12 +21,40 @@ pub struct BuiltinAcp {
receiver_group: Uuid,
target_scope: ProtoFilter,
search_attrs: Vec<Attribute>,
modify_removed_attrs: Vec<Attribute>,
modify_classes: Vec<EntryClass>,
}
impl Default for BuiltinAcp {
fn default() -> Self {
Self {
classes: Default::default(),
name: Default::default(),
uuid: Default::default(),
description: Default::default(),
receiver_group: Default::default(),
search_attrs: Default::default(),
modify_removed_attrs: Default::default(),
modify_classes: Default::default(),
target_scope: DEFAULT_TARGET_SCOPE.clone(), // evals to matching nothing
}
}
}
impl From<BuiltinAcp> for EntryInitNew {
fn from(value: BuiltinAcp) -> Self {
let mut entry = EntryInitNew::default();
if value.name.is_empty() {
panic!("Builtin ACP has no name! {:?}", value);
}
if value.classes.is_empty() {
panic!("Builtin ACP has no classes! {:?}", value);
}
if DEFAULT_TARGET_SCOPE.clone() == value.target_scope {
panic!("Builtin ACP has an invalid target_scope! {:?}", value);
}
value.classes.into_iter().for_each(|class| {
entry.add_ava(ATTR_CLASS, class.to_value());
});
@ -44,6 +76,12 @@ impl From<BuiltinAcp> for EntryInitNew {
.map(|sa| sa.to_value())
.collect::<Vec<Value>>(),
);
value.modify_removed_attrs.into_iter().for_each(|attr| {
entry.add_ava(Attribute::AcpModifyRemovedAttr.as_ref(), attr.to_value());
});
value.modify_classes.into_iter().for_each(|class| {
entry.add_ava(Attribute::AcpModifyClass.as_ref(), class.to_value());
});
entry
}
}
@ -67,35 +105,31 @@ lazy_static! {
Attribute::Uuid,
Attribute::LastModifiedCid,
],
..Default::default()
};
}
lazy_static! {
pub static ref E_IDM_ADMINS_ACP_REVIVE_V1: EntryInitNew = entry_init!(
(ATTR_CLASS, EntryClass::Object.to_value()),
(ATTR_CLASS, EntryClass::AccessControlProfile.to_value()),
(ATTR_CLASS, EntryClass::AccessControlModify.to_value()),
(ATTR_NAME, Value::new_iname("idm_admins_acp_revive")),
(ATTR_UUID, Value::Uuid(UUID_IDM_ADMINS_ACP_REVIVE_V1)),
(
Attribute::Description.as_ref(),
Value::new_utf8s("Builtin IDM admin recycle bin revive permission.")
),
(ATTR_ACP_RECEIVER_GROUP, Value::Refer(UUID_SYSTEM_ADMINS)),
(
ATTR_ACP_TARGET_SCOPE,
Value::JsonFilt(ProtoFilter::Eq(
ATTR_CLASS.to_string(),
ATTR_RECYCLED.to_string()
))
),
(ATTR_ACP_MODIFY_REMOVEDATTR, Attribute::Class.to_value()),
(ATTR_ACP_MODIFY_CLASS, EntryClass::Recycled.to_value())
);
pub static ref IDM_ADMINS_ACP_REVIVE_V1: BuiltinAcp = BuiltinAcp {
uuid: UUID_IDM_ADMINS_ACP_REVIVE_V1,
name: "idm_admins_acp_revive",
description: "Builtin IDM admin recycle bin revive permission.",
classes: vec![
EntryClass::Object,
EntryClass::AccessControlProfile,
EntryClass::AccessControlModify,
],
receiver_group: UUID_SYSTEM_ADMINS,
target_scope: ProtoFilter::Eq(ATTR_CLASS.to_string(), ATTR_RECYCLED.to_string()),
modify_removed_attrs: vec![Attribute::Class],
search_attrs: vec![],
modify_classes: vec![EntryClass::Recycled],
};
}
lazy_static! {
pub static ref E_IDM_SELF_ACP_READ_V1: EntryInitNew = entry_init!(
pub static ref E_IDM_SELF_ACP_READ_V1: EntryInitNew =
entry_init!(
(ATTR_CLASS, EntryClass::Object.to_value()),
(ATTR_CLASS, EntryClass::AccessControlProfile.to_value()),
(ATTR_CLASS, EntryClass::AccessControlSearch.to_value()),
@ -110,8 +144,7 @@ lazy_static! {
(ATTR_ACP_RECEIVER_GROUP, Value::Refer(UUID_IDM_ALL_ACCOUNTS)),
(
ATTR_ACP_TARGET_SCOPE,
Value::new_json_filter_s("\"self\"").expect("Invalid JSON filter")
// Value::JsonFilt(ProtoFilter::SelfUuid)
Value::JsonFilt(ProtoFilter::SelfUuid)
),
(ATTR_ACP_SEARCH_ATTR, Attribute::Class.to_value()),
(ATTR_ACP_SEARCH_ATTR, Attribute::Name.to_value()),

File diff suppressed because it is too large Load diff

View file

@ -666,11 +666,11 @@ impl ModifyEvent {
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub fn new_impersonate_entry_ser(
e: &str,
e: BuiltinAccount,
filter: Filter<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
) -> Self {
let ei: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(e);
let ei: EntryInitNew = e.into();
ModifyEvent {
ident: Identity::from_impersonate_entry_readwrite(Arc::new(ei.into_sealed_committed())),
filter: filter.clone().into_valid(),

View file

@ -146,7 +146,7 @@ macro_rules! try_from_entry {
}};
}
#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub struct Account {
// Later these could be &str if we cache entry here too ...
// They can't because if we mod the entry, we'll lose the ref.
@ -807,13 +807,14 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
#[cfg(test)]
mod tests {
use crate::idm::account::Account;
use crate::prelude::*;
use kanidm_proto::v1::UiHint;
#[test]
fn test_idm_account_from_anonymous() {
let anon_e = entry_to_account!(E_ANONYMOUS_V1.clone());
debug!("{:?}", anon_e);
let account: Account = BUILTIN_ACCOUNT_ANONYMOUS_V1.clone().into();
debug!("{:?}", account);
// I think that's it? we may want to check anonymous mech ...
}

View file

@ -1251,6 +1251,7 @@ mod tests {
use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP};
use crate::credential::{BackupCodes, Credential};
use crate::idm::account::Account;
use crate::idm::audit::AuditEvent;
use crate::idm::authsession::{
AuthSession, BAD_AUTH_TYPE_MSG, BAD_BACKUPCODE_MSG, BAD_PASSWORD_MSG, BAD_TOTP_MSG,
@ -1288,7 +1289,7 @@ mod tests {
let webauthn = create_webauthn();
let anon_account = entry_to_account!(E_ANONYMOUS_V1.clone());
let anon_account: Account = BUILTIN_ACCOUNT_ANONYMOUS_V1.clone().into();
let (session, state) = AuthSession::new(
anon_account,
@ -1361,7 +1362,7 @@ mod tests {
fn start_session_simple_password_mech(privileged: bool) -> UserAuthToken {
let webauthn = create_webauthn();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1.clone());
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
// manually load in a cred
let p = CryptoPolicy::minimum();
let cred = Credential::new_password_only(&p, "test_password").unwrap();
@ -1463,7 +1464,7 @@ mod tests {
let jws_signer = create_jwt_signer();
let webauthn = create_webauthn();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1.clone());
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
// manually load in a cred
let p = CryptoPolicy::minimum();
let cred = Credential::new_password_only(&p, "list@no3IBTyqHu$bad").unwrap();
@ -1566,7 +1567,7 @@ mod tests {
let webauthn = create_webauthn();
let jws_signer = create_jwt_signer();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1);
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);
@ -1756,7 +1757,7 @@ mod tests {
let webauthn = create_webauthn();
let jws_signer = create_jwt_signer();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1);
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);
@ -1933,7 +1934,7 @@ mod tests {
let (audit_tx, mut audit_rx) = unbounded();
let ts = duration_from_epoch_now();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1.clone());
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let (webauthn, mut wa, wan_cred) = setup_webauthn_passkey(account.name.as_str());
let jws_signer = create_jwt_signer();
@ -2092,7 +2093,7 @@ mod tests {
let (audit_tx, mut audit_rx) = unbounded();
let ts = duration_from_epoch_now();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1);
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.name.as_str());
let jws_signer = create_jwt_signer();
@ -2299,7 +2300,7 @@ mod tests {
let (audit_tx, mut audit_rx) = unbounded();
let ts = duration_from_epoch_now();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1);
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.name.as_str());
let jws_signer = create_jwt_signer();
@ -2586,7 +2587,7 @@ mod tests {
let jws_signer = create_jwt_signer();
let webauthn = create_webauthn();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1);
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);
@ -2809,7 +2810,7 @@ mod tests {
let webauthn = create_webauthn();
let jws_signer = create_jwt_signer();
// create the ent
let mut account = entry_to_account!(E_ADMIN_V1);
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);

View file

@ -60,29 +60,6 @@ macro_rules! setup_test {
}};
}
#[cfg(test)]
macro_rules! entry_to_account {
($entry:expr) => {{
use std::iter::once;
use crate::entry::{Entry, EntryInvalid, EntryNew};
use crate::idm::account::Account;
use crate::value::Value;
let mut e: Entry<EntryInvalid, EntryNew> = $entry.clone().into_invalid_new();
// Add spn, because normally this is generated but in tests we can't.
let spn = e
.get_ava_single_iname(Attribute::Name.as_ref())
.map(|s| Value::new_spn_str(s, "example.com"))
.expect("Failed to munge spn from name!");
e.set_ava(Attribute::Spn.as_ref(), once(spn));
let e = e.into_sealed_committed();
Account::try_from_entry_no_groups(&e).expect("Account conversion failure")
}};
}
// Test helpers for all plugins.
// #[macro_export]
#[cfg(test)]

View file

@ -1846,7 +1846,7 @@ async fn test_repl_increment_consumer_lagging_attributes(
drop(server_b_txn);
}
// Test two synchronised nodes where no changes occured in a TS/RUV window.
// Test two synchronised nodes where no changes occurred in a TS/RUV window.
#[qs_pair_test]
async fn test_repl_increment_consumer_ruv_trim_past_valid(
server_a: &QueryServer,

View file

@ -603,12 +603,12 @@ impl<'a> QueryServerWriteTransaction<'a> {
// Check the admin object exists (migrations).
// Create the default idm_admin group.
let admin_entries = [
E_ANONYMOUS_V1.clone(),
E_ADMIN_V1.clone(),
E_IDM_ADMIN_V1.clone(),
E_IDM_ADMINS_V1.clone(),
E_SYSTEM_ADMINS_V1.clone(),
let admin_entries: Vec<EntryInitNew> = vec![
BUILTIN_ACCOUNT_ANONYMOUS_V1.clone().into(),
BUILTIN_ACCOUNT_ADMIN.clone().into(),
BUILTIN_ACCOUNT_IDM_ADMIN.clone().into(),
BUILTIN_GROUP_IDM_ADMINS_V1.clone().try_into()?,
BUILTIN_GROUP_SYSTEM_ADMINS_V1.clone().try_into()?,
];
let res: Result<(), _> = admin_entries
.into_iter()
@ -621,53 +621,52 @@ impl<'a> QueryServerWriteTransaction<'a> {
res?;
// Create any system default schema entries.
// Create any system default access profile entries.
let idm_entries = [
// Builtin dyn groups,
JSON_IDM_ALL_PERSONS,
JSON_IDM_ALL_ACCOUNTS,
// Builtin groups
JSON_IDM_PEOPLE_MANAGE_PRIV_V1,
JSON_IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1,
JSON_IDM_PEOPLE_EXTEND_PRIV_V1,
JSON_IDM_PEOPLE_SELF_WRITE_MAIL_PRIV_V1,
JSON_IDM_PEOPLE_WRITE_PRIV_V1,
JSON_IDM_PEOPLE_READ_PRIV_V1,
JSON_IDM_HP_PEOPLE_EXTEND_PRIV_V1,
JSON_IDM_HP_PEOPLE_WRITE_PRIV_V1,
JSON_IDM_HP_PEOPLE_READ_PRIV_V1,
JSON_IDM_GROUP_MANAGE_PRIV_V1,
JSON_IDM_GROUP_WRITE_PRIV_V1,
JSON_IDM_GROUP_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACCOUNT_MANAGE_PRIV_V1,
JSON_IDM_ACCOUNT_WRITE_PRIV_V1,
JSON_IDM_ACCOUNT_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACCOUNT_READ_PRIV_V1,
JSON_IDM_RADIUS_SECRET_WRITE_PRIV_V1,
JSON_IDM_RADIUS_SECRET_READ_PRIV_V1,
JSON_IDM_RADIUS_SERVERS_V1,
let idm_entries: Vec<&BuiltinGroup> = vec![
&IDM_ALL_PERSONS,
&IDM_ALL_ACCOUNTS,
&IDM_PEOPLE_MANAGE_PRIV_V1,
&IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1,
&IDM_PEOPLE_EXTEND_PRIV_V1,
&IDM_PEOPLE_SELF_WRITE_MAIL_PRIV_V1,
&IDM_PEOPLE_WRITE_PRIV_V1,
&IDM_PEOPLE_READ_PRIV_V1,
&IDM_HP_PEOPLE_EXTEND_PRIV_V1,
&IDM_HP_PEOPLE_WRITE_PRIV_V1,
&IDM_HP_PEOPLE_READ_PRIV_V1,
&IDM_GROUP_MANAGE_PRIV_V1,
&IDM_GROUP_WRITE_PRIV_V1,
&IDM_GROUP_UNIX_EXTEND_PRIV_V1,
&IDM_ACCOUNT_MANAGE_PRIV_V1,
&IDM_ACCOUNT_WRITE_PRIV_V1,
&IDM_ACCOUNT_UNIX_EXTEND_PRIV_V1,
&IDM_ACCOUNT_READ_PRIV_V1,
&IDM_RADIUS_SECRET_WRITE_PRIV_V1,
&IDM_RADIUS_SECRET_READ_PRIV_V1,
&IDM_RADIUS_SERVERS_V1,
// Write deps on read, so write must be added first.
JSON_IDM_HP_ACCOUNT_MANAGE_PRIV_V1,
JSON_IDM_HP_ACCOUNT_WRITE_PRIV_V1,
JSON_IDM_HP_ACCOUNT_READ_PRIV_V1,
JSON_IDM_HP_ACCOUNT_UNIX_EXTEND_PRIV_V1,
JSON_IDM_SCHEMA_MANAGE_PRIV_V1,
JSON_IDM_HP_GROUP_MANAGE_PRIV_V1,
JSON_IDM_HP_GROUP_WRITE_PRIV_V1,
JSON_IDM_HP_GROUP_UNIX_EXTEND_PRIV_V1,
JSON_IDM_ACP_MANAGE_PRIV_V1,
JSON_DOMAIN_ADMINS,
JSON_IDM_HP_OAUTH2_MANAGE_PRIV_V1,
JSON_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV,
JSON_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV,
&IDM_HP_ACCOUNT_MANAGE_PRIV_V1,
&IDM_HP_ACCOUNT_WRITE_PRIV_V1,
&IDM_HP_ACCOUNT_READ_PRIV_V1,
&IDM_HP_ACCOUNT_UNIX_EXTEND_PRIV_V1,
&IDM_SCHEMA_MANAGE_PRIV_V1,
&IDM_HP_GROUP_MANAGE_PRIV_V1,
&IDM_HP_GROUP_WRITE_PRIV_V1,
&IDM_HP_GROUP_UNIX_EXTEND_PRIV_V1,
&IDM_ACP_MANAGE_PRIV_V1,
&DOMAIN_ADMINS,
&IDM_HP_OAUTH2_MANAGE_PRIV_V1,
&IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV,
&IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV,
// All members must exist before we write HP
JSON_IDM_HIGH_PRIVILEGE_V1,
&IDM_HIGH_PRIVILEGE_V1,
// other things
&IDM_UI_ENABLE_EXPERIMENTAL_FEATURES,
&IDM_ACCOUNT_MAIL_READ_PRIV,
];
let res: Result<(), _> = idm_entries
.iter()
.try_for_each(|e_str| self.internal_migrate_or_create_str(e_str));
.into_iter()
.try_for_each(|e| self.internal_migrate_or_create(e.clone().try_into()?));
if res.is_ok() {
admin_debug!("initialise_idm -> result Ok!");
} else {
@ -679,7 +678,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
let idm_entries: Vec<EntryInitNew> = vec![
// Built in access controls.
IDM_ADMINS_ACP_RECYCLE_SEARCH_V1.clone().into(),
E_IDM_ADMINS_ACP_REVIVE_V1.clone(),
IDM_ADMINS_ACP_REVIVE_V1.clone().into(),
E_IDM_ALL_ACP_READ_V1.clone(),
E_IDM_SELF_ACP_READ_V1.clone(),
E_IDM_SELF_ACP_WRITE_V1.clone(),
@ -718,8 +717,6 @@ impl<'a> QueryServerWriteTransaction<'a> {
E_IDM_ACP_RADIUS_SECRET_WRITE_PRIV_V1.clone(),
E_IDM_HP_ACP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_V1.clone(),
E_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1.clone(),
E_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES.clone(),
E_IDM_ACCOUNT_MAIL_READ_PRIV.clone(),
E_IDM_ACP_ACCOUNT_MAIL_READ_PRIV_V1.clone(),
E_IDM_ACCOUNT_SELF_ACP_WRITE_V1.clone(),
];

View file

@ -556,8 +556,9 @@ mod tests {
assert!(server_txn.modify(&me_emp) == Err(OperationError::EmptyRequest));
// Mod changes no objects
// TODO: @yaleman fix this because we don't have a way to do this anymore
let me_nochg = ModifyEvent::new_impersonate_entry_ser(
JSON_ADMIN_V1,
BUILTIN_ACCOUNT_IDM_ADMIN.clone(),
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("flarbalgarble")