diff --git a/Cargo.lock b/Cargo.lock index 3751429ec..17a9f6045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2872,6 +2872,7 @@ dependencies = [ "ldap3_client", "serde", "serde_json", + "sketching", "tokio", "toml", "tracing", diff --git a/examples/iam_migration_ldap.toml b/examples/iam_migration_ldap.toml new file mode 100644 index 000000000..7ff91d64b --- /dev/null +++ b/examples/iam_migration_ldap.toml @@ -0,0 +1,11 @@ +debug = false +dry_run = true +schedule = "* * * * *" +skip_root_check = false +sync_token="cheesemonkey" +ldap_uri="ldaps://example.com:636" +ldap_ca="" +ldap_sync_dn="" +ldap_sync_pw="" +ldap_sync_base_dn="" +ldap_filter="(objectClass=posixAccount)" \ No newline at end of file diff --git a/libs/client/src/lib.rs b/libs/client/src/lib.rs index aeb43c4da..03eda807d 100644 --- a/libs/client/src/lib.rs +++ b/libs/client/src/lib.rs @@ -32,6 +32,7 @@ pub use reqwest::StatusCode; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::error::Error as SerdeJsonError; +use serde_json::json; use tokio::sync::{Mutex, RwLock}; use url::Url; use uuid::Uuid; @@ -108,6 +109,32 @@ impl Display for KanidmClientBuilder { } } +#[test] +fn test_kanidmclientbuilder_display() { + let foo = KanidmClientBuilder::default(); + println!("{}", foo.to_string()); + assert!(foo.to_string().contains("verify_ca")); + + let foo = KanidmClientBuilder { + address: Some("https://example.com".to_string()), + verify_ca: true, + verify_hostnames: true, + ca: None, + connect_timeout: Some(420), + use_system_proxies: true, + }; + println!("foo {}", foo.to_string()); + assert!(foo.to_string().contains("verify_ca: true")); + assert!(foo.to_string().contains("verify_hostnames: true")); + + let badness = foo.danger_accept_invalid_hostnames(true); + let badness = badness.danger_accept_invalid_certs(true); + println!("badness: {}", badness.to_string()); + assert!(badness.to_string().contains("verify_ca: false")); + assert!(badness.to_string().contains("verify_hostnames: false")); + +} + #[derive(Debug)] pub struct KanidmClient { pub(crate) client: reqwest::Client, @@ -890,7 +917,8 @@ impl KanidmClient { let response = self .client .delete(self.make_url(dest)) - .header(CONTENT_TYPE, APPLICATION_JSON); + // empty-ish body that makes the parser happy + .json(&json!([])); let response = { let tguard = self.bearer_token.read().await; @@ -932,12 +960,10 @@ impl KanidmClient { dest: &str, request: R, ) -> Result<(), ClientError> { - let req_string = serde_json::to_string(&request).map_err(ClientError::JsonEncode)?; let response = self .client .delete(self.make_url(dest)) - .body(req_string) - .header(CONTENT_TYPE, APPLICATION_JSON); + .json(&request); let response = { let tguard = self.bearer_token.read().await; @@ -1647,6 +1673,7 @@ impl KanidmClient { .await } + // TODO: add test coverage pub async fn idm_account_credential_update_accept_sha1_totp( &self, session_token: &CUSessionToken, @@ -1666,6 +1693,7 @@ impl KanidmClient { .await } + // TODO: add test coverage pub async fn idm_account_credential_update_backup_codes_generate( &self, session_token: &CUSessionToken, @@ -1675,6 +1703,7 @@ impl KanidmClient { .await } + // TODO: add test coverage pub async fn idm_account_credential_update_primary_remove( &self, session_token: &CUSessionToken, @@ -1704,6 +1733,7 @@ impl KanidmClient { .await } + // TODO: add test coverage pub async fn idm_account_credential_update_passkey_remove( &self, session_token: &CUSessionToken, diff --git a/libs/client/src/scim.rs b/libs/client/src/scim.rs index c614e84e8..6102da465 100644 --- a/libs/client/src/scim.rs +++ b/libs/client/src/scim.rs @@ -2,10 +2,12 @@ use crate::{ClientError, KanidmClient}; use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState}; impl KanidmClient { + // TODO: testing for this pub async fn scim_v1_sync_status(&self) -> Result { self.perform_get_request("/scim/v1/Sync").await } + // TODO: testing for this pub async fn scim_v1_sync_update( &self, scim_sync_request: &ScimSyncRequest, diff --git a/libs/client/src/service_account.rs b/libs/client/src/service_account.rs index 5a48d0123..7fdbf59f9 100644 --- a/libs/client/src/service_account.rs +++ b/libs/client/src/service_account.rs @@ -146,8 +146,11 @@ impl KanidmClient { pub async fn idm_service_account_unix_extend( &self, + // The username or uuid of the account id: &str, + // The GID number to set for the account gidnumber: Option, + // Set a default login shell shell: Option<&str>, ) -> Result<(), ClientError> { let ux = AccountUnixExtend { @@ -158,6 +161,7 @@ impl KanidmClient { .await } + // TODO: test coverage for this, but there's a weird issue with ACPs on apply pub async fn idm_service_account_into_person(&self, id: &str) -> Result<(), ClientError> { self.perform_post_request( format!("/v1/service_account/{}/_into_person", id).as_str(), diff --git a/libs/client/src/sync_account.rs b/libs/client/src/sync_account.rs index 547770dc2..da2b6e507 100644 --- a/libs/client/src/sync_account.rs +++ b/libs/client/src/sync_account.rs @@ -78,6 +78,7 @@ impl KanidmClient { .await } + /// Creates a sync token for a given sync account pub async fn idm_sync_account_generate_token( &self, id: &str, diff --git a/proto/src/constants.rs b/proto/src/constants.rs index 9443ff6f3..c203a775f 100644 --- a/proto/src/constants.rs +++ b/proto/src/constants.rs @@ -168,6 +168,7 @@ pub const OAUTH2_SCOPE_READ: &str = "read"; pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement"; pub const LDAP_ATTR_CN: &str = "cn"; +pub const LDAP_ATTR_DISPLAY_NAME: &str = "displayName"; pub const LDAP_ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative"; pub const LDAP_ATTR_EMAIL_PRIMARY: &str = "emailprimary"; pub const LDAP_ATTR_ENTRYDN: &str = "entrydn"; @@ -181,6 +182,7 @@ pub const LDAP_ATTR_MEMBER: &str = "member"; pub const LDAP_ATTR_NAME: &str = "name"; pub const LDAP_ATTR_OBJECTCLASS: &str = "objectClass"; pub const LDAP_ATTR_OU: &str = "ou"; +pub const LDAP_CLASS_GROUPOFNAMES: &str = "groupofnames"; // rust can't deal with this being compiled out, don't try and #[cfg()] them pub const TEST_ATTR_NON_EXIST: &str = "non-exist"; diff --git a/proto/src/v1.rs b/proto/src/v1.rs index 6ee9546a0..02deba5b5 100644 --- a/proto/src/v1.rs +++ b/proto/src/v1.rs @@ -273,6 +273,8 @@ pub enum OperationError { ReplDomainLevelUnsatisfiable, ReplDomainUuidMismatch, TransactionAlreadyCommitted, + /// when you ask for a gid that's lower than a safe minimum + GidOverlapsSystemMin(u32), } impl PartialEq for OperationError { diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs index 12247aba8..6f5e60926 100644 --- a/server/core/src/https/v1.rs +++ b/server/core/src/https/v1.rs @@ -896,6 +896,7 @@ pub async fn account_id_radius_token( } /// Expects an `AccountUnixExtend` object +/// #[instrument(name = "account_post_id_unix", level = "INFO", skip(id, state, kopid))] pub async fn account_post_id_unix( State(state): State, diff --git a/server/lib/benches/scaling_10k.rs b/server/lib/benches/scaling_10k.rs index 870d46ae1..ae898e478 100644 --- a/server/lib/benches/scaling_10k.rs +++ b/server/lib/benches/scaling_10k.rs @@ -1,4 +1,4 @@ -use std::time::{Duration, Instant}; +use std::time::{Duration, Instant, SystemTime}; use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput, @@ -7,9 +7,15 @@ use criterion::{ use kanidmd_lib::entry::{Entry, EntryInit, EntryNew}; use kanidmd_lib::entry_init; use kanidmd_lib::prelude::{Attribute, EntryClass}; -use kanidmd_lib::utils::duration_from_epoch_now; use kanidmd_lib::value::Value; +pub fn duration_from_epoch_now() -> Duration { + #[allow(clippy::expect_used)] + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("invalid duration from epoch now") +} + pub fn scaling_user_create_single(c: &mut Criterion) { let mut group = c.benchmark_group("user_create_single"); group.sample_size(10); diff --git a/server/lib/src/constants/entries.rs b/server/lib/src/constants/entries.rs index abc074d45..7701d704c 100644 --- a/server/lib/src/constants/entries.rs +++ b/server/lib/src/constants/entries.rs @@ -621,6 +621,18 @@ impl From for &'static str { } } +impl AsRef for EntryClass { + fn as_ref(&self) -> &str { + self.into() + } +} + +impl From<&EntryClass> for &'static str { + fn from(value: &EntryClass) -> Self { + (*value).into() + } +} + impl From for String { fn from(val: EntryClass) -> Self { let s: &'static str = val.into(); diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index 76d60fb86..edc646117 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -897,10 +897,10 @@ mod tests { assert_entry_contains!( lsre, "spn=testperson1@example.com,dc=example,dc=com", - (Attribute::ObjectClass, "object"), - (Attribute::ObjectClass, "person"), - (Attribute::ObjectClass, "account"), - (Attribute::ObjectClass, "posixaccount"), + (Attribute::ObjectClass, EntryClass::Object.as_ref()), + (Attribute::ObjectClass, EntryClass::Person.as_ref()), + (Attribute::ObjectClass, EntryClass::Account.as_ref()), + (Attribute::ObjectClass, EntryClass::PosixAccount.as_ref()), (Attribute::DisplayName, "testperson1"), (Attribute::Name, "testperson1"), (Attribute::GidNumber, "12345678"), diff --git a/server/lib/src/macros.rs b/server/lib/src/macros.rs index dd3ead1de..d218fbe03 100644 --- a/server/lib/src/macros.rs +++ b/server/lib/src/macros.rs @@ -119,16 +119,22 @@ macro_rules! run_create_test { }}; } -// #[macro_export] #[cfg(test)] +/// Runs a test with preloaded entries, then modifies based on a filter/list, then runs a given check macro_rules! run_modify_test { ( + // expected outcome $expect:expr, + // things to preload $preload_entries:ident, + // the targets to modify $modify_filter:expr, + // changes to make $modify_list:expr, $internal:expr, + // something to run after the preload but before the modification, takes `&mut qs_write` $pre_hook:expr, + // the result we expect $check:expr ) => {{ use crate::be::{Backend, BackendConfig}; diff --git a/server/lib/src/plugins/attrunique.rs b/server/lib/src/plugins/attrunique.rs index 0dc0851c4..41c698a46 100644 --- a/server/lib/src/plugins/attrunique.rs +++ b/server/lib/src/plugins/attrunique.rs @@ -498,15 +498,12 @@ mod tests { // Test entry in db, and same name, reject. #[test] fn test_pre_create_name_unique() { - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["person"], - "name": ["testperson"], - "description": ["testperson"], - "displayname": ["testperson"] - } - }"#, + + let e: Entry = entry_init!( + (Attribute::Class, EntryClass::Person.to_value()), + (Attribute::Name, Value::new_iname("testperson")), + (Attribute::Description, Value::new_utf8s("testperson")), + (Attribute::DisplayName, Value::new_utf8s("testperson")) ); let create = vec![e.clone()]; @@ -526,15 +523,11 @@ mod tests { // Test two entries in create that would have same name, reject. #[test] fn test_pre_create_name_unique_2() { - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["person"], - "name": ["testperson"], - "description": ["testperson"], - "displayname": ["testperson"] - } - }"#, + let e: Entry = entry_init!( + (Attribute::Class, EntryClass::Person.to_value()), + (Attribute::Name, Value::new_iname("testperson")), + (Attribute::Description, Value::new_utf8s("testperson")), + (Attribute::DisplayName, Value::new_utf8s("testperson")) ); let create = vec![e.clone(), e]; @@ -557,24 +550,15 @@ mod tests { // A mod to something that exists, reject. #[test] fn test_pre_modify_name_unique() { - let ea: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_a"], - "description": ["testgroup"] - } - }"#, + let ea: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_a")), + (Attribute::Description, Value::new_utf8s("testgroup")) ); - - let eb: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_b"], - "description": ["testgroup"] - } - }"#, + let eb: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_b")), + (Attribute::Description, Value::new_utf8s("testgroup")) ); let preload = vec![ea, eb]; @@ -601,24 +585,15 @@ mod tests { // Two items modded to have the same value, reject. #[test] fn test_pre_modify_name_unique_2() { - let ea: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_a"], - "description": ["testgroup"] - } - }"#, + let ea: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_a")), + (Attribute::Description, Value::new_utf8s("testgroup")) ); - - let eb: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_b"], - "description": ["testgroup"] - } - }"#, + let eb: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_b")), + (Attribute::Description, Value::new_utf8s("testgroup")) ); let preload = vec![ea, eb]; diff --git a/server/lib/src/plugins/gidnumber.rs b/server/lib/src/plugins/gidnumber.rs index 19a426f0b..1a46ecc84 100644 --- a/server/lib/src/plugins/gidnumber.rs +++ b/server/lib/src/plugins/gidnumber.rs @@ -35,12 +35,8 @@ fn apply_gidnumber(e: &mut Entry) -> Result<(), Opera let gid = uuid_to_gid_u32(u_ref); // assert the value is greater than the system range. if gid < GID_SYSTEM_NUMBER_MIN { - return Err(OperationError::InvalidAttribute(format!( - "{} {} may overlap with system range {}", - Attribute::GidNumber, - gid, - GID_SYSTEM_NUMBER_MIN - ))); + admin_error!("Requested GID {} is lower than system minimum {}", gid, GID_SYSTEM_NUMBER_MIN); + return Err(OperationError::GidOverlapsSystemMin(GID_SYSTEM_NUMBER_MIN)); } let gid_v = Value::new_uint32(gid); @@ -50,12 +46,8 @@ fn apply_gidnumber(e: &mut Entry) -> Result<(), Opera } else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) { // If they provided us with a gid number, ensure it's in a safe range. if gid <= GID_SAFETY_NUMBER_MIN { - Err(OperationError::InvalidAttribute(format!( - "{} {} overlaps into system secure range {}", - Attribute::GidNumber, - gid, - GID_SAFETY_NUMBER_MIN - ))) + admin_error!("Requested GID {} is lower or equal to a safe value {}", gid, GID_SAFETY_NUMBER_MIN); + Err(OperationError::GidOverlapsSystemMin(GID_SAFETY_NUMBER_MIN)) } else { Ok(()) } @@ -290,9 +282,7 @@ mod tests { let preload = Vec::new(); run_create_test!( - Err(OperationError::InvalidAttribute( - "gidnumber 580 may overlap with system range 65536".to_string() - )), + Err(OperationError::GidOverlapsSystemMin(65536)), preload, create, None, @@ -315,9 +305,7 @@ mod tests { let preload = Vec::new(); run_create_test!( - Err(OperationError::InvalidAttribute( - "gidnumber 500 overlaps into system secure range 1000".to_string() - )), + Err(OperationError::GidOverlapsSystemMin(1000)), preload, create, None, @@ -340,9 +328,7 @@ mod tests { let preload = Vec::new(); run_create_test!( - Err(OperationError::InvalidAttribute( - "gidnumber 0 overlaps into system secure range 1000".to_string() - )), + Err(OperationError::GidOverlapsSystemMin(1000)), preload, create, None, diff --git a/server/lib/src/plugins/refint.rs b/server/lib/src/plugins/refint.rs index 870189c06..c75ec87a7 100644 --- a/server/lib/src/plugins/refint.rs +++ b/server/lib/src/plugins/refint.rs @@ -456,23 +456,24 @@ mod tests { use crate::prelude::*; use crate::value::{Oauth2Session, Session, SessionState}; use time::OffsetDateTime; - use uuid::uuid; use crate::credential::Credential; use kanidm_lib_crypto::CryptoPolicy; + const TEST_TESTGROUP_A_UUID: &str = "d2b496bd-8493-47b7-8142-f568b5cf47ee"; + const TEST_TESTGROUP_B_UUID: &str = "8cef42bc-2cac-43e4-96b3-8f54561885ca"; + // The create references a uuid that doesn't exist - reject #[test] fn test_create_uuid_reference_not_exist() { - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup"], - "description": ["testperson"], - "member": ["ca85168c-91b7-49a8-b7bb-a3d5bb40e97e"] - } - }"#, + let e = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Member, + Value::Refer(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap()) + ) ); let create = vec![e]; @@ -491,26 +492,23 @@ mod tests { // The create references a uuid that does exist - validate #[test] fn test_create_uuid_reference_exist() { - let ea: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_a"], - "description": ["testgroup"], - "uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let ea = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_a")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Uuid, + Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); - - let eb: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_b"], - "description": ["testgroup"], - "member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let eb = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_b")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Member, + Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); let preload = vec![ea]; @@ -604,7 +602,7 @@ mod tests { )), ModifyList::new_list(vec![Modify::Present( Attribute::Member.into(), - Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap() + Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap() )]), None, |_| {}, @@ -638,7 +636,7 @@ mod tests { )), ModifyList::new_list(vec![Modify::Present( Attribute::Member.into(), - Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap() + Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap() )]), None, |_| {}, @@ -685,7 +683,7 @@ mod tests { ModifyList::new_list(vec![ Modify::Present( Attribute::Member.into(), - Value::Refer(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee")) + Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) ), Modify::Present(Attribute::Member.into(), Value::Refer(UUID_DOES_NOT_EXIST)), ]), @@ -761,7 +759,7 @@ mod tests { )), ModifyList::new_list(vec![Modify::Present( Attribute::Member.into(), - Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap() + Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap() )]), None, |_| {}, @@ -806,7 +804,7 @@ mod tests { )), ModifyList::new_list(vec![Modify::Present( Attribute::Member.into(), - Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap() + Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap() )]), None, |qs: &mut QueryServerWriteTransaction| { @@ -827,26 +825,23 @@ mod tests { // This is the valid case, where the reference is MAY. #[test] fn test_delete_remove_referent_valid() { - let ea: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_a"], - "description": ["testgroup"], - "uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let ea: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_a")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Uuid, + Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); - - let eb: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_b"], - "description": ["testgroup"], - "member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let eb: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_b")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Member, + Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); let preload = vec![ea, eb]; @@ -867,31 +862,30 @@ mod tests { // // this is the invalid case, where the reference is MUST. #[test] - fn test_delete_remove_referent_invalid() {} + fn test_delete_remove_referent_invalid() { + // TODO: uh.. wot + } // Delete of something that holds references. #[test] fn test_delete_remove_referee() { - let ea: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_a"], - "description": ["testgroup"], - "uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let ea: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_a")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Uuid, + Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); - - let eb: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_b"], - "description": ["testgroup"], - "member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let eb: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_b")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Member, + Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); let preload = vec![ea, eb]; @@ -911,18 +905,19 @@ mod tests { // Delete something that has a self reference. #[test] fn test_delete_remove_reference_self() { - let eb: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["group"], - "name": ["testgroup_b"], - "description": ["testgroup"], - "uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"], - "member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] - } - }"#, + let eb: Entry = entry_init!( + (Attribute::Class, EntryClass::Group.to_value()), + (Attribute::Name, Value::new_iname("testgroup_b")), + (Attribute::Description, Value::new_utf8s("testgroup")), + ( + Attribute::Uuid, + Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ), + ( + Attribute::Member, + Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap()) + ) ); - let preload = vec![eb]; run_delete_test!( @@ -964,7 +959,7 @@ mod tests { ( Attribute::OAuth2RsScopeMap, Value::new_oauthscopemap( - uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"), + Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap(), btreeset![OAUTH2_SCOPE_READ.to_string()] ) .expect("Invalid scope") @@ -976,7 +971,7 @@ mod tests { (Attribute::Name, Value::new_iname("testgroup")), ( Attribute::Uuid, - Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")) + Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap()) ), (Attribute::Description, Value::new_utf8s("testgroup")) ); @@ -1015,7 +1010,7 @@ mod tests { // Create a user let mut server_txn = server.write(curtime).await; - let tuuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); + let tuuid = Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap(); let rs_uuid = Uuid::new_v4(); let e1 = entry_init!( diff --git a/server/lib/src/plugins/spn.rs b/server/lib/src/plugins/spn.rs index 9184e8ffe..6c97f2f92 100644 --- a/server/lib/src/plugins/spn.rs +++ b/server/lib/src/plugins/spn.rs @@ -225,16 +225,13 @@ mod tests { #[test] fn test_spn_generate_create() { - // on create don't provide - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["account", "service_account"], - "name": ["testperson"], - "description": ["testperson"], - "displayname": ["testperson"] - } - }"#, + // on create don't provide the spn, we generate it. + let e: Entry = entry_init!( + (Attribute::Class, EntryClass::Account.to_value()), + (Attribute::Class, EntryClass::ServiceAccount.to_value()), + (Attribute::Name, Value::new_iname("testperson")), + (Attribute::Description, Value::new_utf8s("testperson")), + (Attribute::DisplayName, Value::new_utf8s("testperson")) ); let create = vec![e]; @@ -252,16 +249,13 @@ mod tests { #[test] fn test_spn_generate_modify() { - // on a purge of the spen, generate it. - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["account", "service_account"], - "name": ["testperson"], - "description": ["testperson"], - "displayname": ["testperson"] - } - }"#, + // on a purge of the spn, generate it. + let e: Entry = entry_init!( + (Attribute::Class, EntryClass::Account.to_value()), + (Attribute::Class, EntryClass::ServiceAccount.to_value()), + (Attribute::Name, Value::new_iname("testperson")), + (Attribute::Description, Value::new_utf8s("testperson")), + (Attribute::DisplayName, Value::new_utf8s("testperson")) ); let preload = vec![e]; @@ -280,16 +274,14 @@ mod tests { #[test] fn test_spn_validate_create() { // on create providing invalid spn, we over-write it. - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["account", "service_account"], - "spn": ["testperson@invalid_domain.com"], - "name": ["testperson"], - "description": ["testperson"], - "displayname": ["testperson"] - } - }"#, + + let e: Entry = entry_init!( + (Attribute::Class, EntryClass::Account.to_value()), + (Attribute::Class, EntryClass::ServiceAccount.to_value()), + (Attribute::Spn, Value::new_utf8s("testperson@invalid_domain.com")), + (Attribute::Name, Value::new_iname("testperson")), + (Attribute::Description, Value::new_utf8s("testperson")), + (Attribute::DisplayName, Value::new_utf8s("testperson")) ); let create = vec![e]; @@ -307,15 +299,13 @@ mod tests { #[test] fn test_spn_validate_modify() { // On modify (removed/present) of the spn, just regenerate it. - let e: Entry = Entry::unsafe_from_entry_str( - r#"{ - "attrs": { - "class": ["account", "service_account"], - "name": ["testperson"], - "description": ["testperson"], - "displayname": ["testperson"] - } - }"#, + + let e: Entry = entry_init!( + (Attribute::Class, EntryClass::Account.to_value()), + (Attribute::Class, EntryClass::ServiceAccount.to_value()), + (Attribute::Name, Value::new_iname("testperson")), + (Attribute::Description, Value::new_utf8s("testperson")), + (Attribute::DisplayName, Value::new_utf8s("testperson")) ); let preload = vec![e]; diff --git a/server/lib/src/schema.rs b/server/lib/src/schema.rs index ef4ad39c8..e937a11aa 100644 --- a/server/lib/src/schema.rs +++ b/server/lib/src/schema.rs @@ -2903,7 +2903,7 @@ mod tests { assert_eq!( e_service_person.validate(&schema), Err(SchemaError::ExcludesNotSatisfied( - vec!["person".to_string()] + vec![EntryClass::Person.to_string()] )) ); diff --git a/server/lib/src/value.rs b/server/lib/src/value.rs index 33f194dd4..41884f5df 100644 --- a/server/lib/src/value.rs +++ b/server/lib/src/value.rs @@ -1703,7 +1703,7 @@ impl Value { } } Value::Spn(n, r) => format!("{n}@{r}"), - _ => unreachable!(), + _ => unreachable!("You've specified the wrong type for the attribute, got: {:?}", self), } } diff --git a/server/testkit/tests/integration.rs b/server/testkit/tests/integration.rs index 8d993eab0..3d83e5c30 100644 --- a/server/testkit/tests/integration.rs +++ b/server/testkit/tests/integration.rs @@ -1,5 +1,8 @@ //! Integration tests using browser automation +use kanidm_client::KanidmClient; +use kanidmd_testkit::login_put_admin_idm_admins; + /// Tries to handle closing the webdriver session if there's an error #[allow(unused_macros)] macro_rules! handle_error { @@ -197,3 +200,22 @@ async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) { } // tokio::time::sleep(Duration::from_millis(3000)).await; } + +#[kanidmd_testkit::test] +async fn test_domain_reset_token_key(rsclient: KanidmClient) { + login_put_admin_idm_admins(&rsclient).await; + assert!(rsclient.idm_domain_reset_token_key().await.is_ok()); +} + +#[kanidmd_testkit::test] +async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) { + login_put_admin_idm_admins(&rsclient).await; + assert!(rsclient + .idm_domain_set_ldap_basedn("dc=krabsarekool,dc=example,dc=com") + .await + .is_ok()); + assert!(rsclient + .idm_domain_set_ldap_basedn("krabsarekool") + .await + .is_err()); +} diff --git a/server/testkit/tests/proto_v1_test.rs b/server/testkit/tests/proto_v1_test.rs index c402199fc..d82d5b238 100644 --- a/server/testkit/tests/proto_v1_test.rs +++ b/server/testkit/tests/proto_v1_test.rs @@ -22,7 +22,7 @@ use webauthn_authenticator_rs::softpasskey::SoftPasskey; use webauthn_authenticator_rs::WebauthnAuthenticator; use kanidm_client::{ClientError, KanidmClient}; -use kanidmd_testkit::ADMIN_TEST_PASSWORD; +use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER}; const UNIX_TEST_PASSWORD: &str = "unix test user password"; @@ -1410,29 +1410,36 @@ async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) { #[kanidmd_testkit::test] async fn test_server_api_token_lifecycle(rsclient: KanidmClient) { let res = rsclient - .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) .await; assert!(res.is_ok()); + let test_service_account_username = "test_service"; + // Not recommended in production! rsclient - .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &[ADMIN_TEST_USER]) .await .unwrap(); rsclient - .idm_service_account_create("test_service", "Test Service") + .idm_service_account_create(test_service_account_username, "Test Service") .await .expect("Failed to create service account"); let tokens = rsclient - .idm_service_account_list_api_token("test_service") + .idm_service_account_list_api_token(test_service_account_username) .await .expect("Failed to list service account api tokens"); assert!(tokens.is_empty()); let token = rsclient - .idm_service_account_generate_api_token("test_service", "test token", None, false) + .idm_service_account_generate_api_token( + test_service_account_username, + "test token", + None, + false, + ) .await .expect("Failed to create service account api token"); @@ -1445,7 +1452,7 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) { .expect("Embedded jwk not found"); let tokens = rsclient - .idm_service_account_list_api_token("test_service") + .idm_service_account_list_api_token(test_service_account_username) .await .expect("Failed to list service account api tokens"); @@ -1457,24 +1464,134 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) { .expect("Failed to destroy service account api token"); let tokens = rsclient - .idm_service_account_list_api_token("test_service") + .idm_service_account_list_api_token(test_service_account_username) .await .expect("Failed to list service account api tokens"); assert!(tokens.is_empty()); + // test we can add an attribute + assert!(rsclient + .idm_service_account_add_attr( + test_service_account_username, + Attribute::Mail.as_ref(), + &vec!["test@example.com"] + ) + .await + .is_ok()); + + // test we can overwrite an attribute + let new_displayname = vec!["testing displayname 1235"]; + assert!(rsclient + .idm_service_account_set_attr( + test_service_account_username, + Attribute::DisplayName.as_ref(), + &new_displayname + ) + .await + .is_ok()); + // check it actually set + let displayname = rsclient + .idm_service_account_get_attr( + test_service_account_username, + Attribute::DisplayName.as_ref(), + ) + .await + .expect("Failed to get displayname") + .expect("Failed to unwrap displayname"); + assert!(new_displayname == displayname); + + rsclient + .idm_service_account_purge_attr(test_service_account_username, Attribute::Mail.as_ref()) + .await + .expect("Failed to purge displayname"); + + assert!(rsclient + .idm_service_account_get_attr(test_service_account_username, Attribute::Mail.as_ref(),) + .await + .expect("Failed to check mail attr") + .is_none()); + + assert!(rsclient + .idm_service_account_unix_extend( + test_service_account_username, + Some(58008), + Some("/bin/vim") + ) + .await + .is_ok()); + + assert!(rsclient + .idm_service_account_unix_extend( + test_service_account_username, + Some(1000), + Some("/bin/vim") + ) + .await + .is_err()); + + // because you have to set *something* + assert!(rsclient + .idm_service_account_update(test_service_account_username, None, None, None) + .await + .is_err()); + assert!(rsclient + .idm_service_account_update( + test_service_account_username, + Some(&format!("{}lol", test_service_account_username)), + Some(&format!("{}displayzzzz", test_service_account_username)), + Some(&[format!("{}@example.crabs", test_service_account_username)]), + ) + .await + .is_err()); + + let pw = rsclient.idm_service_account_generate_password(test_service_account_username).await.expect("Failed to get a pw for the service account"); + + assert!(!pw.is_empty()); + assert!(pw.is_ascii()); + + let res = rsclient.idm_service_account_get_credential_status(test_service_account_username).await; + dbg!(&res); + assert!(res.is_ok()); + + println!( + "testing deletion of service account {}", + test_service_account_username + ); + assert!(rsclient + .idm_service_account_delete(test_service_account_username) + .await + .is_ok()); + + + + + // let's create one and just yolo it into a person + // TODO: Turns out this doesn't work because admin doesn't have the right perms to remove `jws_es256_private_key` from the account? + // rsclient + // .idm_service_account_create(test_service_account_username, "Test Service") + // .await + // .expect("Failed to create service account"); + + // rsclient.idm_service_account_into_person(test_service_account_username).await.expect("Failed to convert service account into person"); + + // assert!(rsclient + // .idm_person_account_delete(test_service_account_username) + // .await + // .is_ok()); + // No need to test expiry, that's validated in the server internal tests. } #[kanidmd_testkit::test] async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) { let res = rsclient - .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) .await; assert!(res.is_ok()); // Not recommended in production! rsclient - .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &[ADMIN_TEST_USER]) .await .unwrap(); @@ -1553,7 +1670,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) { // Since the session is revoked, check with the admin. let res = rsclient - .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) .await; assert!(res.is_ok()); @@ -1564,6 +1681,18 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) { assert!(tokens.is_empty()); // No need to test expiry, that's validated in the server internal tests. + + // testing idm_account_credential_update_cancel_mfareg + let (token, _status) = rsclient + .idm_account_credential_update_begin("demo_account") + .await + .expect("Failed to get token for demo_account"); + + println!("trying to cancel the token we just got"); + assert!(rsclient + .idm_account_credential_update_cancel_mfareg(&token) + .await + .is_ok()); } #[kanidmd_testkit::test] @@ -1793,3 +1922,21 @@ async fn test_server_user_auth_privileged_shortcut(rsclient: KanidmClient) { } } } + +// wanna test how long it takes for testkit to start up? here's your biz. +// turns out as of 2023-10-11 on my M2 Max, it's about 1.0 seconds per iteration +// #[kanidmd_testkit::test] +// fn test_teskit_test_test() { +// #[allow(unnameable_test_items)] + +// for _ in 0..15 { +// #[kanidmd_testkit::test] +// #[allow(dead_code)] +// async fn test_teskit_test(rsclient: KanidmClient){ +// assert!(rsclient.auth_anonymous().await.is_ok()); +// } + +// tk_test_teskit_test(); +// } + +// } diff --git a/server/testkit/tests/scim_test.rs b/server/testkit/tests/scim_test.rs index 0eec9dee9..f5d8f8678 100644 --- a/server/testkit/tests/scim_test.rs +++ b/server/testkit/tests/scim_test.rs @@ -1,7 +1,7 @@ use compact_jwt::JwsUnverified; use kanidm_client::KanidmClient; use kanidm_proto::internal::ScimSyncToken; -use kanidmd_testkit::ADMIN_TEST_PASSWORD; +use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER}; use reqwest::header::HeaderValue; use std::str::FromStr; use url::Url; @@ -9,7 +9,7 @@ use url::Url; #[kanidmd_testkit::test] async fn test_sync_account_lifecycle(rsclient: KanidmClient) { let a_res = rsclient - .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) .await; assert!(a_res.is_ok()); diff --git a/tools/cli/src/cli/serviceaccount.rs b/tools/cli/src/cli/serviceaccount.rs index 0aa3f3bda..782397ef5 100644 --- a/tools/cli/src/cli/serviceaccount.rs +++ b/tools/cli/src/cli/serviceaccount.rs @@ -497,6 +497,7 @@ impl ServiceAccountOpt { } }, // end ServiceAccountOpt::Validity ServiceAccountOpt::IntoPerson(aopt) => { + warn!("This command is deprecated and will be removed in a future release"); let client = aopt.copt.to_client(OpType::Write).await; match client .idm_service_account_into_person(aopt.aopts.account_id.as_str()) diff --git a/tools/cli/src/opt/kanidm.rs b/tools/cli/src/opt/kanidm.rs index 8810a399b..f5ff5331b 100644 --- a/tools/cli/src/opt/kanidm.rs +++ b/tools/cli/src/opt/kanidm.rs @@ -515,7 +515,7 @@ pub enum ServiceAccountOpt { #[clap(subcommand)] commands: AccountValidity, }, - /// Convert a service account into a person. This is used during the alpha.9 + /// (Deprecated - due for removal in v1.1.0-15) - Convert a service account into a person. This is used during the alpha.9 /// to alpha.10 migration to "fix up" accounts that were not previously marked /// as persons. #[clap(name = "into-person")] diff --git a/tools/iam_migrations/freeipa/src/main.rs b/tools/iam_migrations/freeipa/src/main.rs index 9e357b6a4..5c7c79e71 100644 --- a/tools/iam_migrations/freeipa/src/main.rs +++ b/tools/iam_migrations/freeipa/src/main.rs @@ -23,8 +23,8 @@ use base64urlsafedata::Base64UrlSafeData; use chrono::Utc; use clap::Parser; use cron::Schedule; -use kanidm_proto::constants::{LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS}; -use kanidmd_lib::prelude::Attribute; +use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_CLASS_GROUPOFNAMES}; +use kanidmd_lib::prelude::{Attribute, EntryClass}; use std::collections::BTreeMap; use std::fs::metadata; use std::fs::File; @@ -583,7 +583,7 @@ async fn process_ipa_sync_result( .entry .attrs .get(LDAP_ATTR_OBJECTCLASS) - .map(|oc| oc.contains("person")) + .map(|oc| oc.contains(EntryClass::Person.as_ref())) .unwrap_or_default() { user_dns.push(dn.clone()); @@ -638,7 +638,7 @@ async fn process_ipa_sync_result( // We have to split the DN to it's RDN because lol. dn.split_once(',') .and_then(|(rdn, _)| rdn.split_once('=')) - .map(|(_, uid)| LdapFilter::Equality("uid".to_string(), uid.to_string())) + .map(|(_, uid)| LdapFilter::Equality(ATTR_UID.to_string(), uid.to_string())) }) .collect(); @@ -903,7 +903,7 @@ fn ipa_to_scim_entry( } .into(), )) - } else if oc.contains("groupofnames") { + } else if oc.contains(LDAP_CLASS_GROUPOFNAMES) { let LdapSyncReplEntry { entry_uuid, state: _, diff --git a/tools/iam_migrations/ldap/Cargo.toml b/tools/iam_migrations/ldap/Cargo.toml index ff0964776..516869f91 100644 --- a/tools/iam_migrations/ldap/Cargo.toml +++ b/tools/iam_migrations/ldap/Cargo.toml @@ -40,3 +40,6 @@ kanidm_utils_users = { workspace = true } [build-dependencies] clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true } + +[dev-dependencies] +sketching = { workspace = true } diff --git a/tools/iam_migrations/ldap/src/config.rs b/tools/iam_migrations/ldap/src/config.rs index a635221d0..e6827bd77 100644 --- a/tools/iam_migrations/ldap/src/config.rs +++ b/tools/iam_migrations/ldap/src/config.rs @@ -1,4 +1,5 @@ -use kanidmd_lib::prelude::Attribute; +use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_CN, LDAP_CLASS_GROUPOFNAMES}; +use kanidmd_lib::prelude::{Attribute, EntryClass}; use serde::Deserialize; use std::collections::BTreeMap; use url::Url; @@ -7,15 +8,15 @@ use uuid::Uuid; use ldap3_client::proto::LdapFilter; fn person_objectclass() -> String { - "person".to_string() + EntryClass::Person.to_string() } fn person_attr_user_name() -> String { - "uid".to_string() + ATTR_UID.to_string() } fn person_attr_display_name() -> String { - "cn".to_string() + LDAP_ATTR_CN.to_string() } fn person_attr_gidnumber() -> String { @@ -39,7 +40,7 @@ fn person_attr_ssh_public_key() -> String { } fn group_objectclass() -> String { - "groupofnames".to_string() + LDAP_CLASS_GROUPOFNAMES.to_string() } fn group_attr_name() -> String { diff --git a/tools/iam_migrations/ldap/src/main.rs b/tools/iam_migrations/ldap/src/main.rs index 7bdce9901..f37704b9d 100644 --- a/tools/iam_migrations/ldap/src/main.rs +++ b/tools/iam_migrations/ldap/src/main.rs @@ -62,21 +62,21 @@ async fn driver_main(opt: Opt) { let mut f = match File::open(&opt.ldap_sync_config) { Ok(f) => f, Err(e) => { - error!("Unable to open profile file [{:?}] 🥺", e); + error!("Unable to open ldap sync config from '{}' [{:?}] 🥺", &opt.ldap_sync_config.display(), e); return; } }; let mut contents = String::new(); if let Err(e) = f.read_to_string(&mut contents) { - error!("unable to read profile contents {:?}", e); + error!("unable to read file '{}': {:?}", &opt.ldap_sync_config.display(), e); return; }; let sync_config: Config = match toml::from_str(contents.as_str()) { Ok(c) => c, Err(e) => { - eprintln!("unable to parse config {:?}", e); + eprintln!("Unable to parse config from '{}' error: {:?}", &opt.ldap_sync_config.display(), e); return; } }; @@ -781,3 +781,41 @@ fn main() { rt.block_on(async move { driver_main(opt).await }); } + +#[tokio::test] +async fn test_driver_main() { + let testopt = Opt { + client_config: PathBuf::from("test"), + ldap_sync_config: PathBuf::from("test"), + debug: false, + schedule: false, + proto_dump: false, + dry_run: false, + skip_root_check: true, + }; + let _ = sketching::test_init(); + + println!("testing config"); + // because it can't find the profile file it'll just stop + assert_eq!(driver_main(testopt.clone()).await, ()); + println!("done testing missing config"); + + let testopt = Opt{ + client_config: PathBuf::from(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))), + ldap_sync_config: PathBuf::from(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))), + ..testopt + }; + + println!("valid file path, invalid contents"); + assert_eq!(driver_main(testopt.clone()).await, ()); + println!("done with valid file path, invalid contents"); + let testopt = Opt{ + client_config: PathBuf::from(format!("{}/../../../examples/iam_migration_ldap.toml", env!("CARGO_MANIFEST_DIR"))), + ldap_sync_config: PathBuf::from(format!("{}/../../../examples/iam_migration_ldap.toml", env!("CARGO_MANIFEST_DIR"))), + ..testopt + }; + + println!("valid file path, invalid contents"); + assert_eq!(driver_main(testopt).await, ()); + println!("done with valid file path, valid contents"); +} \ No newline at end of file diff --git a/tools/iam_migrations/ldap/src/opt.rs b/tools/iam_migrations/ldap/src/opt.rs index 5b838baa0..de98e14ae 100644 --- a/tools/iam_migrations/ldap/src/opt.rs +++ b/tools/iam_migrations/ldap/src/opt.rs @@ -1,7 +1,7 @@ use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH; pub const DEFAULT_LDAP_CONFIG_PATH: &str = "/etc/kanidm/ldap-sync"; -#[derive(Debug, clap::Parser)] +#[derive(Debug, clap::Parser, Clone)] #[clap(about = "Kanidm LDAP Sync Driver")] pub struct Opt { /// Enable debugging of the sync driver diff --git a/tools/orca/src/ds.rs b/tools/orca/src/ds.rs index a90a45d57..402cf489a 100644 --- a/tools/orca/src/ds.rs +++ b/tools/orca/src/ds.rs @@ -1,4 +1,5 @@ use hashbrown::{HashMap, HashSet}; +use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_DISPLAY_NAME, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_ATTR_OU, LDAP_ATTR_GROUPS}; use std::time::{Duration, Instant}; use ldap3_proto::proto::*; @@ -59,7 +60,7 @@ impl DirectoryServer { let filter = LdapFilter::Or( targets .iter() - .map(|u| LdapFilter::Equality("cn".to_string(), u.to_string())) + .map(|u| LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string())) .collect(), ); @@ -86,7 +87,7 @@ impl DirectoryServer { // Check if ou=people and ou=group exist let res = self .ldap - .search(LdapFilter::Equality("ou".to_string(), "people".to_string())) + .search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(), "people".to_string())) .await?; if res.is_empty() { @@ -96,14 +97,14 @@ impl DirectoryServer { dn: format!("ou=people,{}", self.ldap.basedn), attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec![ "top".as_bytes().into(), "organizationalUnit".as_bytes().into(), ], }, LdapAttribute { - atype: "ou".to_string(), + atype: LDAP_ATTR_OU.to_string(), vals: vec!["people".as_bytes().into()], }, ], @@ -113,25 +114,25 @@ impl DirectoryServer { let res = self .ldap - .search(LdapFilter::Equality("ou".to_string(), "groups".to_string())) + .search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(),LDAP_ATTR_GROUPS.to_string())) .await?; if res.is_empty() { // Doesn't exist - info!("Creating ou=groups"); - let ou_groups = LdapAddRequest { - dn: format!("ou=groups,{}", self.ldap.basedn), + info!("Creating ou={}", LDAP_ATTR_GROUPS); + let ou_groups: LdapAddRequest = LdapAddRequest { + dn: format!("ou={},{}", LDAP_ATTR_GROUPS, self.ldap.basedn), attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec![ "top".as_bytes().into(), "organizationalUnit".as_bytes().into(), ], }, LdapAttribute { - atype: "ou".to_string(), - vals: vec!["groups".as_bytes().into()], + atype: LDAP_ATTR_OU.to_string(), + vals: vec![LDAP_ATTR_GROUPS.as_bytes().into()], }, ], }; @@ -144,7 +145,7 @@ impl DirectoryServer { // does it already exist? let res = self .ldap - .search(LdapFilter::Equality("cn".to_string(), u.to_string())) + .search(LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string())) .await?; if !res.is_empty() { @@ -159,7 +160,7 @@ impl DirectoryServer { dn, attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec![ "top".as_bytes().into(), "nsPerson".as_bytes().into(), @@ -169,15 +170,15 @@ impl DirectoryServer { ], }, LdapAttribute { - atype: "cn".to_string(), + atype: LDAP_ATTR_CN.to_string(), vals: vec![a.uuid.as_bytes().to_vec()], }, LdapAttribute { - atype: "uid".to_string(), + atype: ATTR_UID.to_string(), vals: vec![a.name.as_bytes().into()], }, LdapAttribute { - atype: "displayName".to_string(), + atype: LDAP_ATTR_DISPLAY_NAME.to_string(), vals: vec![a.display_name.as_bytes().into()], }, LdapAttribute { @@ -205,14 +206,14 @@ impl DirectoryServer { dn, attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec![ "top".as_bytes().into(), "groupOfNames".as_bytes().into(), ], }, LdapAttribute { - atype: "cn".to_string(), + atype: LDAP_ATTR_CN.to_string(), vals: vec![g.uuid.as_bytes().to_vec(), g.name.as_bytes().into()], }, ], @@ -268,7 +269,7 @@ impl DirectoryServer { let res = self .ldap .search(LdapFilter::Equality( - "cn".to_string(), + LDAP_ATTR_CN.to_string(), "priv_account_manage".to_string(), )) .await?; @@ -280,11 +281,11 @@ impl DirectoryServer { dn: format!("cn=priv_account_manage,{}", self.ldap.basedn), attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()], }, LdapAttribute { - atype: "cn".to_string(), + atype: LDAP_ATTR_CN.to_string(), vals: vec!["priv_account_manage".as_bytes().into()], }, ], @@ -295,7 +296,7 @@ impl DirectoryServer { let res = self .ldap .search(LdapFilter::Equality( - "cn".to_string(), + LDAP_ATTR_CN.to_string(), "priv_group_manage".to_string(), )) .await?; @@ -307,11 +308,11 @@ impl DirectoryServer { dn: format!("cn=priv_group_manage,{}", self.ldap.basedn), attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()], }, LdapAttribute { - atype: "cn".to_string(), + atype: LDAP_ATTR_CN.to_string(), vals: vec!["priv_group_manage".as_bytes().into()], }, ], diff --git a/tools/orca/src/ipa.rs b/tools/orca/src/ipa.rs index dd69fcab4..ae819b854 100644 --- a/tools/orca/src/ipa.rs +++ b/tools/orca/src/ipa.rs @@ -1,4 +1,5 @@ use hashbrown::{HashMap, HashSet}; +use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_DISPLAY_NAME, LDAP_CLASS_GROUPOFNAMES, LDAP_ATTR_OBJECTCLASS, LDAP_ATTR_CN}; use ldap3_proto::proto::*; use std::time::{Duration, Instant}; use uuid::Uuid; @@ -93,7 +94,7 @@ impl IpaServer { dn, attributes: vec![ LdapAttribute { - atype: "objectClass".to_string(), + atype: LDAP_ATTR_OBJECTCLASS.to_string(), vals: vec![ "ipaobject".as_bytes().into(), "person".as_bytes().into(), @@ -113,11 +114,11 @@ impl IpaServer { vals: vec!["autogenerate".as_bytes().into()], }, LdapAttribute { - atype: "uid".to_string(), + atype: ATTR_UID.to_string(), vals: vec![a.name.as_bytes().into()], }, LdapAttribute { - atype: "cn".to_string(), + atype: LDAP_ATTR_CN.to_string(), vals: vec![a.name.as_bytes().into()], }, LdapAttribute { @@ -129,7 +130,7 @@ impl IpaServer { vals: vec![a.name.as_bytes().into()], }, LdapAttribute { - atype: "displayName".to_string(), + atype: LDAP_ATTR_DISPLAY_NAME.to_string(), vals: vec![a.display_name.as_bytes().into()], }, LdapAttribute { @@ -200,7 +201,7 @@ impl IpaServer { atype: "objectClass".to_string(), vals: vec![ "top".as_bytes().into(), - "groupofnames".as_bytes().into(), + LDAP_CLASS_GROUPOFNAMES.as_bytes().into(), "nestedgroup".as_bytes().into(), "ipausergroup".as_bytes().into(), "ipaobject".as_bytes().into(),