diff --git a/kanidmd/lib-macros/src/entry.rs b/kanidmd/lib-macros/src/entry.rs index df3d4f2ab..ca779814c 100644 --- a/kanidmd/lib-macros/src/entry.rs +++ b/kanidmd/lib-macros/src/entry.rs @@ -97,3 +97,84 @@ pub(crate) fn qs_test(_args: &TokenStream, item: TokenStream, with_init: bool) - result.into() } + +pub(crate) fn idm_test(_args: &TokenStream, item: TokenStream) -> TokenStream { + let input: syn::ItemFn = match syn::parse(item.clone()) { + Ok(it) => it, + Err(e) => return token_stream_with_error(item, e), + }; + + if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) { + let msg = "second test attribute is supplied"; + return token_stream_with_error(item, syn::Error::new_spanned(&attr, msg)); + }; + + if input.sig.asyncness.is_none() { + let msg = "the `async` keyword is missing from the function declaration"; + return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg)); + } + + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, _last_stmt_end_span) = { + let mut last_stmt = input + .block + .stmts + .last() + .map(ToTokens::into_token_stream) + .unwrap_or_default() + .into_iter(); + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + + let rt = quote_spanned! {last_stmt_start_span=> + tokio::runtime::Builder::new_current_thread() + }; + + let header = quote! { + #[::core::prelude::v1::test] + }; + + let test_fn = &input.sig.ident; + let test_driver = Ident::new(&format!("idm_{}", test_fn), input.sig.span()); + + // Effectively we are just injecting a real test function around this which we will + // call. + + let result = quote! { + #input + + #header + fn #test_driver() { + let body = async { + let (test_server, mut idms_delayed) = crate::testkit::setup_idm_test().await; + + #test_fn(&test_server, &mut idms_delayed).await; + + // Any needed teardown? + // Make sure there are no errors. + let mut idm_read_txn = test_server.proxy_read().await; + let verifications = idm_read_txn.qs_read.verify(); + trace!("Verification result: {:?}", verifications); + assert!(verifications.len() == 0); + + idms_delayed.check_is_empty_or_panic(); + }; + #[allow(clippy::expect_used, clippy::diverging_sub_expression)] + { + return #rt + .enable_all() + .build() + .expect("Failed building the Runtime") + .block_on(body); + } + } + }; + + result.into() +} diff --git a/kanidmd/lib-macros/src/lib.rs b/kanidmd/lib-macros/src/lib.rs index 5f495f7ce..e2c4b53a3 100644 --- a/kanidmd/lib-macros/src/lib.rs +++ b/kanidmd/lib-macros/src/lib.rs @@ -26,3 +26,8 @@ pub fn qs_test(args: TokenStream, item: TokenStream) -> TokenStream { pub fn qs_test_no_init(args: TokenStream, item: TokenStream) -> TokenStream { entry::qs_test(&args, item, false) } + +#[proc_macro_attribute] +pub fn idm_test(args: TokenStream, item: TokenStream) -> TokenStream { + entry::idm_test(&args, item) +} diff --git a/kanidmd/lib/benches/scaling_10k.rs b/kanidmd/lib/benches/scaling_10k.rs index 80e686112..1c6f27d44 100644 --- a/kanidmd/lib/benches/scaling_10k.rs +++ b/kanidmd/lib/benches/scaling_10k.rs @@ -1,15 +1,11 @@ use std::time::{Duration, Instant}; -use async_std::task; use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput, }; use kanidmd_lib; use kanidmd_lib::entry::{Entry, EntryInit, EntryNew}; use kanidmd_lib::entry_init; -use kanidmd_lib::idm::server::{IdmServer, IdmServerDelayed}; -use kanidmd_lib::macros::run_idm_test_no_logging; -use kanidmd_lib::server::QueryServer; use kanidmd_lib::utils::duration_from_epoch_now; use kanidmd_lib::value::Value; @@ -28,14 +24,19 @@ pub fn scaling_user_create_single(c: &mut Criterion) { println!("iters, size -> {:?}, {:?}", iters, size); for _i in 0..iters { - run_idm_test_no_logging( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let ct = duration_from_epoch_now(); + let mut rt = tokio::runtime::Builder::new_current_thread(); + elapsed = rt + .enable_all() + .build() + .expect("Failed building the Runtime") + .block_on(async { + let (idms, _idms_delayed) = + kanidmd_lib::testkit::setup_idm_test().await; + let ct = duration_from_epoch_now(); let start = Instant::now(); for counter in 0..size { - let idms_prox_write = task::block_on(idms.proxy_write_async(ct)); - + let mut idms_prox_write = idms.proxy_write(ct).await; let name = format!("testperson_{}", counter); let e1 = entry_init!( ("class", Value::new_class("object")), @@ -51,9 +52,8 @@ pub fn scaling_user_create_single(c: &mut Criterion) { idms_prox_write.commit().expect("Must not fail"); } - elapsed = elapsed.checked_add(start.elapsed()).unwrap(); - }, - ); + elapsed.checked_add(start.elapsed()).unwrap() + }); } elapsed }); @@ -92,20 +92,25 @@ pub fn scaling_user_create_batched(c: &mut Criterion) { .collect(); for _i in 0..iters { - kanidmd_lib::macros::run_idm_test_no_logging( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let ct = duration_from_epoch_now(); + let mut rt = tokio::runtime::Builder::new_current_thread(); + elapsed = rt + .enable_all() + .build() + .expect("Failed building the Runtime") + .block_on(async { + let (idms, _idms_delayed) = + kanidmd_lib::testkit::setup_idm_test().await; + let ct = duration_from_epoch_now(); let start = Instant::now(); - let idms_prox_write = task::block_on(idms.proxy_write_async(ct)); + let mut idms_prox_write = idms.proxy_write(ct).await; let cr = idms_prox_write.qs_write.internal_create(data.clone()); assert!(cr.is_ok()); idms_prox_write.commit().expect("Must not fail"); - elapsed = elapsed.checked_add(start.elapsed()).unwrap(); - }, - ); + elapsed.checked_add(start.elapsed()).unwrap() + }); } elapsed }); diff --git a/kanidmd/lib/src/idm/credupdatesession.rs b/kanidmd/lib/src/idm/credupdatesession.rs index 8192e54ec..80d676554 100644 --- a/kanidmd/lib/src/idm/credupdatesession.rs +++ b/kanidmd/lib/src/idm/credupdatesession.rs @@ -1472,7 +1472,6 @@ impl<'a> IdmServerCredUpdateTransaction<'a> { mod tests { use std::time::Duration; - use async_std::task; use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech, CredentialDetailType}; use uuid::uuid; use webauthn_authenticator_rs::softpasskey::SoftPasskey; @@ -1494,120 +1493,119 @@ mod tests { const TEST_CURRENT_TIME: u64 = 6000; const TESTPERSON_UUID: Uuid = uuid!("cf231fea-1a8f-4410-a520-fd9b1a379c86"); - #[test] - fn test_idm_credential_update_session_init() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + #[idm_test] + async fn test_idm_credential_update_session_init( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let mut idms_prox_write = idms.proxy_write(ct).await; - let testaccount_uuid = Uuid::new_v4(); + let testaccount_uuid = Uuid::new_v4(); - let e1 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("account")), - ("class", Value::new_class("service_account")), - ("name", Value::new_iname("user_account_only")), - ("uuid", Value::new_uuid(testaccount_uuid)), - ("description", Value::new_utf8s("testaccount")), - ("displayname", Value::new_utf8s("testaccount")) - ); + let e1 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("service_account")), + ("name", Value::new_iname("user_account_only")), + ("uuid", Value::new_uuid(testaccount_uuid)), + ("description", Value::new_utf8s("testaccount")), + ("displayname", Value::new_utf8s("testaccount")) + ); - let e2 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("account")), - ("class", Value::new_class("person")), - ("name", Value::new_iname("testperson")), - ("uuid", Value::new_uuid(TESTPERSON_UUID)), - ("description", Value::new_utf8s("testperson")), - ("displayname", Value::new_utf8s("testperson")) - ); + let e2 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("person")), + ("name", Value::new_iname("testperson")), + ("uuid", Value::new_uuid(TESTPERSON_UUID)), + ("description", Value::new_utf8s("testperson")), + ("displayname", Value::new_utf8s("testperson")) + ); - let ce = CreateEvent::new_internal(vec![e1, e2]); - let cr = idms_prox_write.qs_write.create(&ce); - assert!(cr.is_ok()); + let ce = CreateEvent::new_internal(vec![e1, e2]); + let cr = idms_prox_write.qs_write.create(&ce); + assert!(cr.is_ok()); - let testaccount = idms_prox_write - .qs_write - .internal_search_uuid(&testaccount_uuid) - .expect("failed"); + let testaccount = idms_prox_write + .qs_write + .internal_search_uuid(&testaccount_uuid) + .expect("failed"); - let testperson = idms_prox_write - .qs_write - .internal_search_uuid(&TESTPERSON_UUID) - .expect("failed"); + let testperson = idms_prox_write + .qs_write + .internal_search_uuid(&TESTPERSON_UUID) + .expect("failed"); - let idm_admin = idms_prox_write - .qs_write - .internal_search_uuid(&UUID_IDM_ADMIN) - .expect("failed"); + let idm_admin = idms_prox_write + .qs_write + .internal_search_uuid(&UUID_IDM_ADMIN) + .expect("failed"); - // user without permission - fail - // - accounts don't have self-write permission. + // user without permission - fail + // - accounts don't have self-write permission. - let cur = idms_prox_write.init_credential_update( - &InitCredentialUpdateEvent::new_impersonate_entry(testaccount), - ct, - ); + let cur = idms_prox_write.init_credential_update( + &InitCredentialUpdateEvent::new_impersonate_entry(testaccount), + ct, + ); - assert!(matches!(cur, Err(OperationError::NotAuthorised))); + assert!(matches!(cur, Err(OperationError::NotAuthorised))); - // user with permission - success + // user with permission - success - let cur = idms_prox_write.init_credential_update( - &InitCredentialUpdateEvent::new_impersonate_entry(testperson), - ct, - ); + let cur = idms_prox_write.init_credential_update( + &InitCredentialUpdateEvent::new_impersonate_entry(testperson), + ct, + ); - assert!(cur.is_ok()); + assert!(cur.is_ok()); - // create intent token without permission - fail + // create intent token without permission - fail - // create intent token with permission - success + // create intent token with permission - success - let cur = idms_prox_write.init_credential_update_intent( - &InitCredentialUpdateIntentEvent::new_impersonate_entry( - idm_admin, - TESTPERSON_UUID, - MINIMUM_INTENT_TTL, - ), - ct, - ); + let cur = idms_prox_write.init_credential_update_intent( + &InitCredentialUpdateIntentEvent::new_impersonate_entry( + idm_admin, + TESTPERSON_UUID, + MINIMUM_INTENT_TTL, + ), + ct, + ); - assert!(cur.is_ok()); - let intent_tok = cur.expect("Failed to create intent token!"); + assert!(cur.is_ok()); + let intent_tok = cur.expect("Failed to create intent token!"); - // exchange intent token - invalid - fail - // Expired - let cur = idms_prox_write - .exchange_intent_credential_update(intent_tok.clone(), ct + MINIMUM_INTENT_TTL); + // exchange intent token - invalid - fail + // Expired + let cur = idms_prox_write + .exchange_intent_credential_update(intent_tok.clone(), ct + MINIMUM_INTENT_TTL); - assert!(matches!(cur, Err(OperationError::SessionExpired))); + assert!(matches!(cur, Err(OperationError::SessionExpired))); - let cur = idms_prox_write - .exchange_intent_credential_update(intent_tok.clone(), ct + MAXIMUM_INTENT_TTL); + let cur = idms_prox_write + .exchange_intent_credential_update(intent_tok.clone(), ct + MAXIMUM_INTENT_TTL); - assert!(matches!(cur, Err(OperationError::SessionExpired))); + assert!(matches!(cur, Err(OperationError::SessionExpired))); - // exchange intent token - success - let cur = idms_prox_write.exchange_intent_credential_update(intent_tok.clone(), ct); + // exchange intent token - success + let cur = idms_prox_write.exchange_intent_credential_update(intent_tok.clone(), ct); - assert!(cur.is_ok()); + assert!(cur.is_ok()); - // Already used. - let cur = idms_prox_write.exchange_intent_credential_update(intent_tok, ct); + // Already used. + let cur = idms_prox_write.exchange_intent_credential_update(intent_tok, ct); - trace!(?cur); - assert!(cur.is_err()); - }) + trace!(?cur); + assert!(cur.is_err()); } - fn setup_test_session( + async fn setup_test_session( idms: &IdmServer, ct: Duration, ) -> (CredentialUpdateSessionToken, CredentialUpdateSessionStatus) { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + let mut idms_prox_write = idms.proxy_write(ct).await; let e2 = entry_init!( ("class", Value::new_class("object")), @@ -1638,11 +1636,11 @@ mod tests { cur.expect("Failed to start update") } - fn renew_test_session( + async fn renew_test_session( idms: &IdmServer, ct: Duration, ) -> (CredentialUpdateSessionToken, CredentialUpdateSessionStatus) { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + let mut idms_prox_write = idms.proxy_write(ct).await; let testperson = idms_prox_write .qs_write @@ -1659,8 +1657,8 @@ mod tests { cur.expect("Failed to start update") } - fn commit_session(idms: &IdmServer, ct: Duration, cust: CredentialUpdateSessionToken) { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + async fn commit_session(idms: &IdmServer, ct: Duration, cust: CredentialUpdateSessionToken) { + let mut idms_prox_write = idms.proxy_write(ct).await; idms_prox_write .commit_credential_update(&cust, ct) @@ -1669,7 +1667,7 @@ mod tests { idms_prox_write.commit().expect("Failed to commit txn"); } - fn check_testperson_password( + async fn check_testperson_password( idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, pw: &str, @@ -1679,7 +1677,7 @@ mod tests { let auth_init = AuthEvent::named_init("testperson"); - let r1 = task::block_on(idms_auth.auth(&auth_init, ct)); + let r1 = idms_auth.auth(&auth_init, ct).await; let ar = r1.unwrap(); let AuthResult { sessionid, @@ -1694,7 +1692,7 @@ mod tests { let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password); - let r2 = task::block_on(idms_auth.auth(&auth_begin, ct)); + let r2 = idms_auth.auth(&auth_begin, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -1707,7 +1705,7 @@ mod tests { let pw_step = AuthEvent::cred_step_password(sessionid, pw); // Expect success - let r2 = task::block_on(idms_auth.auth(&pw_step, ct)); + let r2 = idms_auth.auth(&pw_step, ct).await; debug!("r2 ==> {:?}", r2); idms_auth.commit().expect("Must not fail"); @@ -1727,7 +1725,7 @@ mod tests { } } - fn check_testperson_password_totp( + async fn check_testperson_password_totp( idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, pw: &str, @@ -1738,7 +1736,7 @@ mod tests { let auth_init = AuthEvent::named_init("testperson"); - let r1 = task::block_on(idms_auth.auth(&auth_init, ct)); + let r1 = idms_auth.auth(&auth_init, ct).await; let ar = r1.unwrap(); let AuthResult { sessionid, @@ -1753,7 +1751,7 @@ mod tests { let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::PasswordMfa); - let r2 = task::block_on(idms_auth.auth(&auth_begin, ct)); + let r2 = idms_auth.auth(&auth_begin, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -1768,7 +1766,7 @@ mod tests { .expect("Failed to perform totp step"); let totp_step = AuthEvent::cred_step_totp(sessionid, totp); - let r2 = task::block_on(idms_auth.auth(&totp_step, ct)); + let r2 = idms_auth.auth(&totp_step, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -1781,7 +1779,7 @@ mod tests { let pw_step = AuthEvent::cred_step_password(sessionid, pw); // Expect success - let r3 = task::block_on(idms_auth.auth(&pw_step, ct)); + let r3 = idms_auth.auth(&pw_step, ct).await; debug!("r3 ==> {:?}", r3); idms_auth.commit().expect("Must not fail"); @@ -1800,7 +1798,7 @@ mod tests { } } - fn check_testperson_password_backup_code( + async fn check_testperson_password_backup_code( idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, pw: &str, @@ -1811,7 +1809,7 @@ mod tests { let auth_init = AuthEvent::named_init("testperson"); - let r1 = task::block_on(idms_auth.auth(&auth_init, ct)); + let r1 = idms_auth.auth(&auth_init, ct).await; let ar = r1.unwrap(); let AuthResult { sessionid, @@ -1826,7 +1824,7 @@ mod tests { let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::PasswordMfa); - let r2 = task::block_on(idms_auth.auth(&auth_begin, ct)); + let r2 = idms_auth.auth(&auth_begin, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -1837,7 +1835,7 @@ mod tests { assert!(matches!(state, AuthState::Continue(_))); let code_step = AuthEvent::cred_step_backup_code(sessionid, code); - let r2 = task::block_on(idms_auth.auth(&code_step, ct)); + let r2 = idms_auth.auth(&code_step, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -1850,7 +1848,7 @@ mod tests { let pw_step = AuthEvent::cred_step_password(sessionid, pw); // Expect success - let r3 = task::block_on(idms_auth.auth(&pw_step, ct)); + let r3 = idms_auth.auth(&pw_step, ct).await; debug!("r3 ==> {:?}", r3); idms_auth.commit().expect("Must not fail"); @@ -1863,7 +1861,7 @@ mod tests { // There now should be a backup code invalidation present let da = idms_delayed.try_recv().expect("invalid"); assert!(matches!(da, DelayedAction::BackupCodeRemoval(_))); - let r = task::block_on(idms.delayed_action(ct, da)); + let r = idms.delayed_action(ct, da).await; assert!(r.is_ok()); // Process the auth session @@ -1875,7 +1873,7 @@ mod tests { } } - fn check_testperson_passkey( + async fn check_testperson_passkey( idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, wa: &mut WebauthnAuthenticator, @@ -1886,7 +1884,7 @@ mod tests { let auth_init = AuthEvent::named_init("testperson"); - let r1 = task::block_on(idms_auth.auth(&auth_init, ct)); + let r1 = idms_auth.auth(&auth_init, ct).await; let ar = r1.unwrap(); let AuthResult { sessionid, @@ -1901,7 +1899,7 @@ mod tests { let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::Passkey); - let r2 = task::block_on(idms_auth.auth(&auth_begin, ct)); + let r2 = idms_auth.auth(&auth_begin, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -1927,7 +1925,7 @@ mod tests { let passkey_step = AuthEvent::cred_step_passkey(sessionid, resp); - let r3 = task::block_on(idms_auth.auth(&passkey_step, ct)); + let r3 = idms_auth.auth(&passkey_step, ct).await; debug!("r3 ==> {:?}", r3); idms_auth.commit().expect("Must not fail"); @@ -1940,7 +1938,7 @@ mod tests { // Process the webauthn update let da = idms_delayed.try_recv().expect("invalid"); assert!(matches!(da, DelayedAction::WebauthnCounterIncrement(_))); - let r = task::block_on(idms.delayed_action(ct, da)); + let r = idms.delayed_action(ct, da).await; assert!(r.is_ok()); // Process the auth session @@ -1953,94 +1951,96 @@ mod tests { } } - #[test] - fn test_idm_credential_update_session_cleanup() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); + #[idm_test] + async fn test_idm_credential_update_session_cleanup( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); - // The session exists - let c_status = cutxn.credential_update_status(&cust, ct); - assert!(c_status.is_ok()); - drop(cutxn); + let cutxn = idms.cred_update_transaction(); + // The session exists + let c_status = cutxn.credential_update_status(&cust, ct); + assert!(c_status.is_ok()); + drop(cutxn); - // Making a new session is what triggers the clean of old sessions. - let _ = renew_test_session(idms, ct + MAXIMUM_CRED_UPDATE_TTL + Duration::from_secs(1)); + // Making a new session is what triggers the clean of old sessions. + let (_cust, _) = + renew_test_session(idms, ct + MAXIMUM_CRED_UPDATE_TTL + Duration::from_secs(1)).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction(); - // Now fake going back in time .... allows the tokne to decrypt, but the sesion - // is gone anyway! - let c_status = cutxn - .credential_update_status(&cust, ct) - .expect_err("Session is still valid!"); - assert!(matches!(c_status, OperationError::InvalidState)); - }) + // Now fake going back in time .... allows the tokne to decrypt, but the sesion + // is gone anyway! + let c_status = cutxn + .credential_update_status(&cust, ct) + .expect_err("Session is still valid!"); + assert!(matches!(c_status, OperationError::InvalidState)); } - #[test] - fn test_idm_credential_update_onboarding_create_new_pw() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let test_pw = - "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; - let ct = Duration::from_secs(TEST_CURRENT_TIME); + #[idm_test] + async fn test_idm_credential_update_onboarding_create_new_pw( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let test_pw = "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; + let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); + let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction(); - // Get the credential status - this should tell - // us the details of the credentials, as well as - // if they are ready and valid to commit? - let c_status = cutxn - .credential_update_status(&cust, ct) - .expect("Failed to get the current session status."); + // Get the credential status - this should tell + // us the details of the credentials, as well as + // if they are ready and valid to commit? + let c_status = cutxn + .credential_update_status(&cust, ct) + .expect("Failed to get the current session status."); - trace!(?c_status); + trace!(?c_status); - assert!(c_status.primary.is_none()); + assert!(c_status.primary.is_none()); - // Test initially creating a credential. - // - pw first - let c_status = cutxn - .credential_primary_set_password(&cust, ct, test_pw) - .expect("Failed to update the primary cred password"); + // Test initially creating a credential. + // - pw first + let c_status = cutxn + .credential_primary_set_password(&cust, ct, test_pw) + .expect("Failed to update the primary cred password"); - assert!(c_status.can_commit); + assert!(c_status.can_commit); - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // Check it works! - assert!(check_testperson_password(idms, idms_delayed, test_pw, ct).is_some()); + // Check it works! + assert!(check_testperson_password(idms, idms_delayed, test_pw, ct) + .await + .is_some()); - // Test deleting the pw - let (cust, _) = renew_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + // Test deleting the pw + let (cust, _) = renew_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - let c_status = cutxn - .credential_update_status(&cust, ct) - .expect("Failed to get the current session status."); - trace!(?c_status); - assert!(c_status.primary.is_some()); + let c_status = cutxn + .credential_update_status(&cust, ct) + .expect("Failed to get the current session status."); + trace!(?c_status); + assert!(c_status.primary.is_some()); - let c_status = cutxn - .credential_primary_delete(&cust, ct) - .expect("Failed to delete the primary cred"); - trace!(?c_status); - assert!(c_status.primary.is_none()); + let c_status = cutxn + .credential_primary_delete(&cust, ct) + .expect("Failed to delete the primary cred"); + trace!(?c_status); + assert!(c_status.primary.is_none()); - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // Must fail now! - assert!(check_testperson_password(idms, idms_delayed, test_pw, ct).is_none()); - } - ) + // Must fail now! + assert!(check_testperson_password(idms, idms_delayed, test_pw, ct) + .await + .is_none()); } // Test set of primary account password @@ -2048,366 +2048,357 @@ mod tests { // - set correctly. // - setup TOTP - #[test] - fn test_idm_credential_update_onboarding_create_new_mfa_totp_basic() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let test_pw = - "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; - let ct = Duration::from_secs(TEST_CURRENT_TIME); + #[idm_test] + async fn test_idm_credential_update_onboarding_create_new_mfa_totp_basic( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let test_pw = "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; + let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + let (cust, _) = setup_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - // Setup the PW - let c_status = cutxn - .credential_primary_set_password(&cust, ct, test_pw) - .expect("Failed to update the primary cred password"); + // Setup the PW + let c_status = cutxn + .credential_primary_set_password(&cust, ct, test_pw) + .expect("Failed to update the primary cred password"); - // Since it's pw only. - assert!(c_status.can_commit); + // Since it's pw only. + assert!(c_status.can_commit); - // - let c_status = cutxn - .credential_primary_init_totp(&cust, ct) - .expect("Failed to update the primary cred password"); + // + let c_status = cutxn + .credential_primary_init_totp(&cust, ct) + .expect("Failed to update the primary cred password"); - // Check the status has the token. - let totp_token: Totp = match c_status.mfaregstate { - MfaRegStateStatus::TotpCheck(secret) => Some(secret.into()), + // Check the status has the token. + let totp_token: Totp = match c_status.mfaregstate { + MfaRegStateStatus::TotpCheck(secret) => Some(secret.into()), - _ => None, - } - .expect("Unable to retrieve totp token, invalid state."); + _ => None, + } + .expect("Unable to retrieve totp token, invalid state."); - trace!(?totp_token); - let chal = totp_token - .do_totp_duration_from_epoch(&ct) - .expect("Failed to perform totp step"); + trace!(?totp_token); + let chal = totp_token + .do_totp_duration_from_epoch(&ct) + .expect("Failed to perform totp step"); - // Intentionally get it wrong. - let c_status = cutxn - .credential_primary_check_totp(&cust, ct, chal + 1) - .expect("Failed to update the primary cred password"); + // Intentionally get it wrong. + let c_status = cutxn + .credential_primary_check_totp(&cust, ct, chal + 1) + .expect("Failed to update the primary cred password"); - assert!(matches!( - c_status.mfaregstate, - MfaRegStateStatus::TotpTryAgain - )); + assert!(matches!( + c_status.mfaregstate, + MfaRegStateStatus::TotpTryAgain + )); - let c_status = cutxn - .credential_primary_check_totp(&cust, ct, chal) - .expect("Failed to update the primary cred password"); + let c_status = cutxn + .credential_primary_check_totp(&cust, ct, chal) + .expect("Failed to update the primary cred password"); - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 0)) - )); + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 0)) + )); - // Should be okay now! + // Should be okay now! - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // Check it works! - assert!(check_testperson_password_totp( - idms, - idms_delayed, - test_pw, - &totp_token, - ct - ) - .is_some()); - // No need to test delete of the whole cred, we already did with pw above. + // Check it works! + assert!( + check_testperson_password_totp(idms, idms_delayed, test_pw, &totp_token, ct) + .await + .is_some() + ); + // No need to test delete of the whole cred, we already did with pw above. - // If we remove TOTP, show it reverts back. - let (cust, _) = renew_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + // If we remove TOTP, show it reverts back. + let (cust, _) = renew_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - let c_status = cutxn - .credential_primary_remove_totp(&cust, ct) - .expect("Failed to update the primary cred password"); + let c_status = cutxn + .credential_primary_remove_totp(&cust, ct) + .expect("Failed to update the primary cred password"); - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::Password) - )); + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::Password) + )); - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // Check it works with totp removed. - assert!(check_testperson_password(idms, idms_delayed, test_pw, ct).is_some()); - } - ) + // Check it works with totp removed. + assert!(check_testperson_password(idms, idms_delayed, test_pw, ct) + .await + .is_some()); } // Check sha1 totp. - #[test] - fn test_idm_credential_update_onboarding_create_new_mfa_totp_sha1() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let test_pw = - "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; - let ct = Duration::from_secs(TEST_CURRENT_TIME); + #[idm_test] + async fn test_idm_credential_update_onboarding_create_new_mfa_totp_sha1( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let test_pw = "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; + let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + let (cust, _) = setup_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - // Setup the PW - let c_status = cutxn - .credential_primary_set_password(&cust, ct, test_pw) - .expect("Failed to update the primary cred password"); + // Setup the PW + let c_status = cutxn + .credential_primary_set_password(&cust, ct, test_pw) + .expect("Failed to update the primary cred password"); - // Since it's pw only. - assert!(c_status.can_commit); + // Since it's pw only. + assert!(c_status.can_commit); - // - let c_status = cutxn - .credential_primary_init_totp(&cust, ct) - .expect("Failed to update the primary cred password"); + // + let c_status = cutxn + .credential_primary_init_totp(&cust, ct) + .expect("Failed to update the primary cred password"); - // Check the status has the token. - let totp_token: Totp = match c_status.mfaregstate { - MfaRegStateStatus::TotpCheck(secret) => Some(secret.into()), + // Check the status has the token. + let totp_token: Totp = match c_status.mfaregstate { + MfaRegStateStatus::TotpCheck(secret) => Some(secret.into()), - _ => None, - } - .expect("Unable to retrieve totp token, invalid state."); + _ => None, + } + .expect("Unable to retrieve totp token, invalid state."); - let totp_token = totp_token.downgrade_to_legacy(); + let totp_token = totp_token.downgrade_to_legacy(); - trace!(?totp_token); - let chal = totp_token - .do_totp_duration_from_epoch(&ct) - .expect("Failed to perform totp step"); + trace!(?totp_token); + let chal = totp_token + .do_totp_duration_from_epoch(&ct) + .expect("Failed to perform totp step"); - // Should getn the warn that it's sha1 - let c_status = cutxn - .credential_primary_check_totp(&cust, ct, chal) - .expect("Failed to update the primary cred password"); + // Should getn the warn that it's sha1 + let c_status = cutxn + .credential_primary_check_totp(&cust, ct, chal) + .expect("Failed to update the primary cred password"); - assert!(matches!( - c_status.mfaregstate, - MfaRegStateStatus::TotpInvalidSha1 - )); + assert!(matches!( + c_status.mfaregstate, + MfaRegStateStatus::TotpInvalidSha1 + )); - // Accept it - let c_status = cutxn - .credential_primary_accept_sha1_totp(&cust, ct) - .expect("Failed to update the primary cred password"); + // Accept it + let c_status = cutxn + .credential_primary_accept_sha1_totp(&cust, ct) + .expect("Failed to update the primary cred password"); - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 0)) - )); + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 0)) + )); - // Should be okay now! + // Should be okay now! - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // Check it works! - assert!(check_testperson_password_totp( - idms, - idms_delayed, - test_pw, - &totp_token, - ct - ) - .is_some()); - // No need to test delete, we already did with pw above. - } - ) + // Check it works! + assert!( + check_testperson_password_totp(idms, idms_delayed, test_pw, &totp_token, ct) + .await + .is_some() + ); + // No need to test delete, we already did with pw above. } - #[test] - fn test_idm_credential_update_onboarding_create_new_mfa_totp_backup_codes() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let test_pw = - "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; - let ct = Duration::from_secs(TEST_CURRENT_TIME); + #[idm_test] + async fn test_idm_credential_update_onboarding_create_new_mfa_totp_backup_codes( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let test_pw = "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; + let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + let (cust, _) = setup_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - // Setup the PW - let _c_status = cutxn - .credential_primary_set_password(&cust, ct, test_pw) - .expect("Failed to update the primary cred password"); + // Setup the PW + let _c_status = cutxn + .credential_primary_set_password(&cust, ct, test_pw) + .expect("Failed to update the primary cred password"); - // Backup codes are refused to be added because we don't have mfa yet. - assert!(matches!( - cutxn.credential_primary_init_backup_codes(&cust, ct), - Err(OperationError::InvalidState) - )); + // Backup codes are refused to be added because we don't have mfa yet. + assert!(matches!( + cutxn.credential_primary_init_backup_codes(&cust, ct), + Err(OperationError::InvalidState) + )); - let c_status = cutxn - .credential_primary_init_totp(&cust, ct) - .expect("Failed to update the primary cred password"); + let c_status = cutxn + .credential_primary_init_totp(&cust, ct) + .expect("Failed to update the primary cred password"); - let totp_token: Totp = match c_status.mfaregstate { - MfaRegStateStatus::TotpCheck(secret) => Some(secret.into()), + let totp_token: Totp = match c_status.mfaregstate { + MfaRegStateStatus::TotpCheck(secret) => Some(secret.into()), - _ => None, - } - .expect("Unable to retrieve totp token, invalid state."); + _ => None, + } + .expect("Unable to retrieve totp token, invalid state."); - trace!(?totp_token); - let chal = totp_token - .do_totp_duration_from_epoch(&ct) - .expect("Failed to perform totp step"); + trace!(?totp_token); + let chal = totp_token + .do_totp_duration_from_epoch(&ct) + .expect("Failed to perform totp step"); - let c_status = cutxn - .credential_primary_check_totp(&cust, ct, chal) - .expect("Failed to update the primary cred password"); + let c_status = cutxn + .credential_primary_check_totp(&cust, ct, chal) + .expect("Failed to update the primary cred password"); - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 0)) - )); + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 0)) + )); - // Now good to go, we need to now add our backup codes. - // Whats the right way to get these back? - let c_status = cutxn - .credential_primary_init_backup_codes(&cust, ct) - .expect("Failed to update the primary cred password"); + // Now good to go, we need to now add our backup codes. + // Whats the right way to get these back? + let c_status = cutxn + .credential_primary_init_backup_codes(&cust, ct) + .expect("Failed to update the primary cred password"); - let codes = match c_status.mfaregstate { - MfaRegStateStatus::BackupCodes(codes) => Some(codes), - _ => None, - } - .expect("Unable to retrieve backupcodes, invalid state."); + let codes = match c_status.mfaregstate { + MfaRegStateStatus::BackupCodes(codes) => Some(codes), + _ => None, + } + .expect("Unable to retrieve backupcodes, invalid state."); - // Should error because the number is not 0 - debug!("{:?}", c_status.primary.as_ref().map(|c| &c.type_)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 8)) - )); + // Should error because the number is not 0 + debug!("{:?}", c_status.primary.as_ref().map(|c| &c.type_)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 8)) + )); - // Should be okay now! - drop(cutxn); - commit_session(idms, ct, cust); + // Should be okay now! + drop(cutxn); + commit_session(idms, ct, cust).await; - let backup_code = codes.iter().next().expect("No codes available"); + let backup_code = codes.iter().next().expect("No codes available"); - // Check it works! - assert!(check_testperson_password_backup_code( - idms, - idms_delayed, - test_pw, - backup_code, - ct - ) - .is_some()); - - // Renew to start the next steps - let (cust, _) = renew_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); - - // Only 7 codes left. - let c_status = cutxn - .credential_update_status(&cust, ct) - .expect("Failed to get the current session status."); - - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 7)) - )); - - // If we remove codes, it leaves totp. - let c_status = cutxn - .credential_primary_remove_backup_codes(&cust, ct) - .expect("Failed to update the primary cred password"); - - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 0)) - )); - - // Re-add the codes. - let c_status = cutxn - .credential_primary_init_backup_codes(&cust, ct) - .expect("Failed to update the primary cred password"); - - assert!(matches!( - c_status.mfaregstate, - MfaRegStateStatus::BackupCodes(_) - )); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::PasswordMfa(true, _, 8)) - )); - - // If we remove totp, it removes codes. - let c_status = cutxn - .credential_primary_remove_totp(&cust, ct) - .expect("Failed to update the primary cred password"); - - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - c_status.primary.as_ref().map(|c| &c.type_), - Some(CredentialDetailType::Password) - )); - - drop(cutxn); - commit_session(idms, ct, cust); - } + // Check it works! + assert!(check_testperson_password_backup_code( + idms, + idms_delayed, + test_pw, + backup_code, + ct ) + .await + .is_some()); + + // Renew to start the next steps + let (cust, _) = renew_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); + + // Only 7 codes left. + let c_status = cutxn + .credential_update_status(&cust, ct) + .expect("Failed to get the current session status."); + + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 7)) + )); + + // If we remove codes, it leaves totp. + let c_status = cutxn + .credential_primary_remove_backup_codes(&cust, ct) + .expect("Failed to update the primary cred password"); + + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 0)) + )); + + // Re-add the codes. + let c_status = cutxn + .credential_primary_init_backup_codes(&cust, ct) + .expect("Failed to update the primary cred password"); + + assert!(matches!( + c_status.mfaregstate, + MfaRegStateStatus::BackupCodes(_) + )); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::PasswordMfa(true, _, 8)) + )); + + // If we remove totp, it removes codes. + let c_status = cutxn + .credential_primary_remove_totp(&cust, ct) + .expect("Failed to update the primary cred password"); + + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + c_status.primary.as_ref().map(|c| &c.type_), + Some(CredentialDetailType::Password) + )); + + drop(cutxn); + commit_session(idms, ct, cust).await; } - #[test] - fn test_idm_credential_update_onboarding_cancel_inprogress_totp() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let test_pw = - "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; - let ct = Duration::from_secs(TEST_CURRENT_TIME); + #[idm_test] + async fn test_idm_credential_update_onboarding_cancel_inprogress_totp( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let test_pw = "fo3EitierohF9AelaNgiem0Ei6vup4equo1Oogeevaetehah8Tobeengae3Ci0ooh0uki"; + let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + let (cust, _) = setup_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - // Setup the PW - let c_status = cutxn - .credential_primary_set_password(&cust, ct, test_pw) - .expect("Failed to update the primary cred password"); + // Setup the PW + let c_status = cutxn + .credential_primary_set_password(&cust, ct, test_pw) + .expect("Failed to update the primary cred password"); - // Since it's pw only. - assert!(c_status.can_commit); + // Since it's pw only. + assert!(c_status.can_commit); - // - let c_status = cutxn - .credential_primary_init_totp(&cust, ct) - .expect("Failed to update the primary cred totp"); + // + let c_status = cutxn + .credential_primary_init_totp(&cust, ct) + .expect("Failed to update the primary cred totp"); - // Check the status has the token. - assert!(c_status.can_commit); - assert!(matches!( - c_status.mfaregstate, - MfaRegStateStatus::TotpCheck(_) - )); + // Check the status has the token. + assert!(c_status.can_commit); + assert!(matches!( + c_status.mfaregstate, + MfaRegStateStatus::TotpCheck(_) + )); - let c_status = cutxn - .credential_update_cancel_mfareg(&cust, ct) - .expect("Failed to cancel inflight totp change"); + let c_status = cutxn + .credential_update_cancel_mfareg(&cust, ct) + .expect("Failed to cancel inflight totp change"); - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(c_status.can_commit); + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(c_status.can_commit); - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // It's pw only, since we canceled TOTP - assert!(check_testperson_password(idms, idms_delayed, test_pw, ct).is_some()); - } - ) + // It's pw only, since we canceled TOTP + assert!(check_testperson_password(idms, idms_delayed, test_pw, ct) + .await + .is_some()); } // Primary cred must be pw or pwmfa @@ -2416,91 +2407,93 @@ mod tests { // - remove webauthn // - test mulitple webauthn token. - #[test] - fn test_idm_credential_update_onboarding_create_new_passkey() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); + #[idm_test] + async fn test_idm_credential_update_onboarding_create_new_passkey( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (cust, _) = setup_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); - let origin = cutxn.get_origin().clone(); + let (cust, _) = setup_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); + let origin = cutxn.get_origin().clone(); - // Create a soft passkey - let mut wa = WebauthnAuthenticator::new(SoftPasskey::new()); + // Create a soft passkey + let mut wa = WebauthnAuthenticator::new(SoftPasskey::new()); - // Start the registration - let c_status = cutxn - .credential_passkey_init(&cust, ct) - .expect("Failed to initiate passkey registration"); + // Start the registration + let c_status = cutxn + .credential_passkey_init(&cust, ct) + .expect("Failed to initiate passkey registration"); - assert!(c_status.passkeys.is_empty()); + assert!(c_status.passkeys.is_empty()); - let passkey_chal = match c_status.mfaregstate { - MfaRegStateStatus::Passkey(c) => Some(c), - _ => None, - } - .expect("Unable to access passkey challenge, invalid state"); + let passkey_chal = match c_status.mfaregstate { + MfaRegStateStatus::Passkey(c) => Some(c), + _ => None, + } + .expect("Unable to access passkey challenge, invalid state"); - let passkey_resp = wa - .do_registration(origin.clone(), passkey_chal) - .expect("Failed to create soft passkey"); + let passkey_resp = wa + .do_registration(origin.clone(), passkey_chal) + .expect("Failed to create soft passkey"); - // Finish the registration - let label = "softtoken".to_string(); - let c_status = cutxn - .credential_passkey_finish(&cust, ct, label, &passkey_resp) - .expect("Failed to initiate passkey registration"); + // Finish the registration + let label = "softtoken".to_string(); + let c_status = cutxn + .credential_passkey_finish(&cust, ct, label, &passkey_resp) + .expect("Failed to initiate passkey registration"); - assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); - assert!(matches!( - // Shuld be none. - c_status.primary.as_ref(), - None - )); + assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None)); + assert!(matches!( + // Shuld be none. + c_status.primary.as_ref(), + None + )); - // Check we have the passkey - trace!(?c_status); - assert!(c_status.passkeys.len() == 1); + // Check we have the passkey + trace!(?c_status); + assert!(c_status.passkeys.len() == 1); - // Get the UUID of the passkey here. - let pk_uuid = c_status.passkeys.get(0).map(|pkd| pkd.uuid).unwrap(); + // Get the UUID of the passkey here. + let pk_uuid = c_status.passkeys.get(0).map(|pkd| pkd.uuid).unwrap(); - // Commit - drop(cutxn); - commit_session(idms, ct, cust); + // Commit + drop(cutxn); + commit_session(idms, ct, cust).await; - // Do an auth test - assert!( - check_testperson_passkey(idms, idms_delayed, &mut wa, origin.clone(), ct) - .is_some() - ); + // Do an auth test + assert!( + check_testperson_passkey(idms, idms_delayed, &mut wa, origin.clone(), ct) + .await + .is_some() + ); - // Now test removing the token - let (cust, _) = renew_test_session(idms, ct); - let cutxn = idms.cred_update_transaction(); + // Now test removing the token + let (cust, _) = renew_test_session(idms, ct).await; + let cutxn = idms.cred_update_transaction(); - trace!(?c_status); - assert!(c_status.primary.is_none()); - assert!(c_status.passkeys.len() == 1); + trace!(?c_status); + assert!(c_status.primary.is_none()); + assert!(c_status.passkeys.len() == 1); - let c_status = cutxn - .credential_passkey_remove(&cust, ct, pk_uuid) - .expect("Failed to delete the primary cred"); + let c_status = cutxn + .credential_passkey_remove(&cust, ct, pk_uuid) + .expect("Failed to delete the primary cred"); - trace!(?c_status); - assert!(c_status.primary.is_none()); - assert!(c_status.passkeys.is_empty()); + trace!(?c_status); + assert!(c_status.primary.is_none()); + assert!(c_status.passkeys.is_empty()); - drop(cutxn); - commit_session(idms, ct, cust); + drop(cutxn); + commit_session(idms, ct, cust).await; - // Must fail now! - assert!( - check_testperson_passkey(idms, idms_delayed, &mut wa, origin, ct).is_none() - ); - } - ) + // Must fail now! + assert!( + check_testperson_passkey(idms, idms_delayed, &mut wa, origin, ct) + .await + .is_none() + ); } // W_ policy, assert can't remove MFA if it's enforced. diff --git a/kanidmd/lib/src/idm/server.rs b/kanidmd/lib/src/idm/server.rs index 3571e9864..cd89e71ea 100644 --- a/kanidmd/lib/src/idm/server.rs +++ b/kanidmd/lib/src/idm/server.rs @@ -1,4 +1,3 @@ -use core::task::{Context, Poll}; use std::convert::TryFrom; use std::str::FromStr; use std::sync::Arc; @@ -11,8 +10,6 @@ use concread::cowcell::{CowCellReadTxn, CowCellWriteTxn}; use concread::hashmap::HashMap; use concread::CowCell; use fernet::Fernet; -// #[cfg(any(test,bench))] -use futures::task as futures_task; use hashbrown::HashSet; use kanidm_proto::v1::{ ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UatPurpose, @@ -343,8 +340,12 @@ impl IdmServer { } impl IdmServerDelayed { - // #[cfg(any(test,bench))] + // I think we can just make this async in the future? + #[cfg(test)] pub(crate) fn check_is_empty_or_panic(&mut self) { + use core::task::{Context, Poll}; + use futures::task as futures_task; + let waker = futures_task::noop_waker(); let mut cx = Context::from_waker(&waker); match self.async_rx.poll_recv(&mut cx) { @@ -365,6 +366,9 @@ impl IdmServerDelayed { #[cfg(test)] pub(crate) fn try_recv(&mut self) -> Result { + use core::task::{Context, Poll}; + use futures::task as futures_task; + let waker = futures_task::noop_waker(); let mut cx = Context::from_waker(&waker); match self.async_rx.poll_recv(&mut cx) { diff --git a/kanidmd/lib/src/idm/serviceaccount.rs b/kanidmd/lib/src/idm/serviceaccount.rs index dfa4b003c..14369fe0b 100644 --- a/kanidmd/lib/src/idm/serviceaccount.rs +++ b/kanidmd/lib/src/idm/serviceaccount.rs @@ -375,98 +375,93 @@ mod tests { use kanidm_proto::v1::ApiToken; use super::{DestroyApiTokenEvent, GenerateApiTokenEvent}; - // use crate::prelude::*; use crate::event::CreateEvent; use crate::idm::server::IdmServerTransaction; - - use async_std::task; + use crate::prelude::*; const TEST_CURRENT_TIME: u64 = 6000; - #[test] - fn test_idm_service_account_api_token() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + GRACE_WINDOW; - let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000); - let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010); - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + #[idm_test] + async fn test_idm_service_account_api_token( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + GRACE_WINDOW; + let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000); + let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010); + let mut idms_prox_write = idms.proxy_write(ct).await; - let testaccount_uuid = Uuid::new_v4(); + let testaccount_uuid = Uuid::new_v4(); - let e1 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("account")), - ("class", Value::new_class("service_account")), - ("name", Value::new_iname("test_account_only")), - ("uuid", Value::new_uuid(testaccount_uuid)), - ("description", Value::new_utf8s("testaccount")), - ("displayname", Value::new_utf8s("testaccount")) - ); + let e1 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("service_account")), + ("name", Value::new_iname("test_account_only")), + ("uuid", Value::new_uuid(testaccount_uuid)), + ("description", Value::new_utf8s("testaccount")), + ("displayname", Value::new_utf8s("testaccount")) + ); - let ce = CreateEvent::new_internal(vec![e1]); - let cr = idms_prox_write.qs_write.create(&ce); - assert!(cr.is_ok()); + let ce = CreateEvent::new_internal(vec![e1]); + let cr = idms_prox_write.qs_write.create(&ce); + assert!(cr.is_ok()); - let gte = GenerateApiTokenEvent::new_internal(testaccount_uuid, "TestToken", Some(exp)); + let gte = GenerateApiTokenEvent::new_internal(testaccount_uuid, "TestToken", Some(exp)); - let api_token = idms_prox_write - .service_account_generate_api_token(>e, ct) - .expect("failed to generate new api token"); + let api_token = idms_prox_write + .service_account_generate_api_token(>e, ct) + .expect("failed to generate new api token"); - trace!(?api_token); + trace!(?api_token); - // Deserialise it. - let apitoken_unverified = - JwsUnverified::from_str(&api_token).expect("Failed to parse apitoken"); - let apitoken_inner: Jws = apitoken_unverified - .validate_embeded() - .expect("Embedded jwk not found"); - let apitoken_inner = apitoken_inner.into_inner(); + // Deserialise it. + let apitoken_unverified = + JwsUnverified::from_str(&api_token).expect("Failed to parse apitoken"); + let apitoken_inner: Jws = apitoken_unverified + .validate_embeded() + .expect("Embedded jwk not found"); + let apitoken_inner = apitoken_inner.into_inner(); - let ident = idms_prox_write - .validate_and_parse_token_to_ident(Some(&api_token), ct) - .expect("Unable to verify api token."); + let ident = idms_prox_write + .validate_and_parse_token_to_ident(Some(&api_token), ct) + .expect("Unable to verify api token."); - assert!(ident.get_uuid() == Some(testaccount_uuid)); + assert!(ident.get_uuid() == Some(testaccount_uuid)); - // Woohoo! Okay lets test the other edge cases. + // Woohoo! Okay lets test the other edge cases. - // Check the expiry - assert!( - idms_prox_write - .validate_and_parse_token_to_ident(Some(&api_token), post_exp) - .expect_err("Should not succeed") - == OperationError::SessionExpired - ); + // Check the expiry + assert!( + idms_prox_write + .validate_and_parse_token_to_ident(Some(&api_token), post_exp) + .expect_err("Should not succeed") + == OperationError::SessionExpired + ); - // Delete session - let dte = DestroyApiTokenEvent::new_internal( - apitoken_inner.account_id, - apitoken_inner.token_id, - ); - assert!(idms_prox_write - .service_account_destroy_api_token(&dte) - .is_ok()); + // Delete session + let dte = + DestroyApiTokenEvent::new_internal(apitoken_inner.account_id, apitoken_inner.token_id); + assert!(idms_prox_write + .service_account_destroy_api_token(&dte) + .is_ok()); - // Within gracewindow? - // This is okay, because we are within the gracewindow. - let ident = idms_prox_write - .validate_and_parse_token_to_ident(Some(&api_token), ct) - .expect("Unable to verify api token."); - assert!(ident.get_uuid() == Some(testaccount_uuid)); + // Within gracewindow? + // This is okay, because we are within the gracewindow. + let ident = idms_prox_write + .validate_and_parse_token_to_ident(Some(&api_token), ct) + .expect("Unable to verify api token."); + assert!(ident.get_uuid() == Some(testaccount_uuid)); - // Past gracewindow? - assert!( - idms_prox_write - .validate_and_parse_token_to_ident(Some(&api_token), past_grc) - .expect_err("Should not succeed") - == OperationError::SessionExpired - ); + // Past gracewindow? + assert!( + idms_prox_write + .validate_and_parse_token_to_ident(Some(&api_token), past_grc) + .expect_err("Should not succeed") + == OperationError::SessionExpired + ); - assert!(idms_prox_write.commit().is_ok()); - }); + assert!(idms_prox_write.commit().is_ok()); } } diff --git a/kanidmd/lib/src/lib.rs b/kanidmd/lib/src/lib.rs index 745196cdf..db6fc4d8f 100644 --- a/kanidmd/lib/src/lib.rs +++ b/kanidmd/lib/src/lib.rs @@ -52,8 +52,7 @@ mod repl; pub mod schema; pub mod server; pub mod status; -#[cfg(test)] -mod testkit; +pub mod testkit; /// A prelude of imports that should be imported by all other Kanidm modules to /// help make imports cleaner. @@ -79,6 +78,7 @@ pub mod prelude { FilterInvalid, FC, }; pub use crate::identity::{AccessScope, IdentType, Identity, IdentityId}; + pub use crate::idm::server::{IdmServer, IdmServerDelayed}; pub use crate::modify::{m_pres, m_purge, m_remove, Modify, ModifyInvalid, ModifyList}; pub use crate::server::{ QueryServer, QueryServerReadTransaction, QueryServerTransaction, diff --git a/kanidmd/lib/src/macros.rs b/kanidmd/lib/src/macros.rs index 89482ffe6..96aa1281f 100644 --- a/kanidmd/lib/src/macros.rs +++ b/kanidmd/lib/src/macros.rs @@ -1,3 +1,4 @@ +#[cfg(test)] macro_rules! setup_test { () => {{ let _ = sketching::test_init(); @@ -71,6 +72,7 @@ macro_rules! entry_str_to_account { }}; } +#[cfg(test)] macro_rules! run_idm_test_inner { ($test_fn:expr) => {{ #[allow(unused_imports)] @@ -112,18 +114,6 @@ macro_rules! run_idm_test { }}; } -pub fn run_idm_test_no_logging(mut test_fn: F) -where - F: FnMut( - &crate::server::QueryServer, - &crate::idm::server::IdmServer, - &crate::idm::server::IdmServerDelayed, - ), -{ - sketching::test_init(); - run_idm_test_inner!(test_fn); -} - // Test helpers for all plugins. // #[macro_export] #[cfg(test)] diff --git a/kanidmd/lib/src/schema.rs b/kanidmd/lib/src/schema.rs index e3e46552c..880593500 100644 --- a/kanidmd/lib/src/schema.rs +++ b/kanidmd/lib/src/schema.rs @@ -1603,6 +1603,7 @@ impl Schema { } } + #[cfg(any(test))] pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> { self.write() } diff --git a/kanidmd/lib/src/server.rs b/kanidmd/lib/src/server.rs index ed07889b5..b3d3ef57e 100644 --- a/kanidmd/lib/src/server.rs +++ b/kanidmd/lib/src/server.rs @@ -820,7 +820,7 @@ impl<'a> QueryServerReadTransaction<'a> { // Verify the data content of the server is as expected. This will probably // call various functions for validation, including possibly plugin // verifications. - fn verify(&mut self) -> Vec> { + pub(crate) fn verify(&mut self) -> Vec> { // If we fail after backend, we need to return NOW because we can't // assert any other faith in the DB states. // * backend diff --git a/kanidmd/lib/src/testkit.rs b/kanidmd/lib/src/testkit.rs index 6a67e2324..00a7607b6 100644 --- a/kanidmd/lib/src/testkit.rs +++ b/kanidmd/lib/src/testkit.rs @@ -19,3 +19,13 @@ pub async fn setup_test() -> QueryServer { // Init is called via the proc macro qs } + +pub async fn setup_idm_test() -> (IdmServer, IdmServerDelayed) { + let qs = setup_test().await; + + qs.initialise_helper(duration_from_epoch_now()) + .await + .expect("init failed!"); + + IdmServer::new(qs, "https://idm.example.com").expect("Failed to setup idms") +}