From 0e57b6f9141aaccd26ad28b319f34b6cfe78dffc Mon Sep 17 00:00:00 2001 From: Firstyear Date: Fri, 3 Mar 2023 17:53:54 +1000 Subject: [PATCH] 1399 some async cleanup (#1421) * More cleanerer * More async! * Fix up tests --- Cargo.lock | 2 - Cargo.toml | 1 - server/core/src/actors/v1_read.rs | 8 +- server/core/src/lib.rs | 4 +- server/lib/Cargo.toml | 1 - server/lib/src/be/idl_sqlite.rs | 1 - server/lib/src/idm/account.rs | 156 +- server/lib/src/idm/applinks.rs | 187 +- server/lib/src/idm/authsession.rs | 18 +- server/lib/src/idm/credupdatesession.rs | 32 +- server/lib/src/idm/ldap.rs | 962 +++--- server/lib/src/idm/oauth2.rs | 3689 +++++++++++------------ server/lib/src/idm/reauth.rs | 4 +- server/lib/src/idm/scim.rs | 1919 ++++++------ server/lib/src/idm/server.rs | 2787 ++++++++--------- server/lib/src/macros.rs | 106 +- server/lib/src/testkit.rs | 4 +- 17 files changed, 4776 insertions(+), 5105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3eadcfa54..1d29900d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,7 +222,6 @@ dependencies = [ "blocking", "futures-lite", "once_cell", - "tokio", ] [[package]] @@ -2410,7 +2409,6 @@ dependencies = [ name = "kanidmd_lib" version = "1.1.0-alpha.12-dev" dependencies = [ - "async-std", "async-trait", "base64 0.13.1", "base64urlsafedata", diff --git a/Cargo.toml b/Cargo.toml index 650e0bbee..5399a4a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ homepage = "https://github.com/kanidm/kanidm/" repository = "https://github.com/kanidm/kanidm/" [workspace.dependencies] -async-std = { version = "^1.12.0", features = ["tokio1"] } async-trait = "^0.1.62" base32 = "^0.4.0" base64 = "^0.13.1" diff --git a/server/core/src/actors/v1_read.rs b/server/core/src/actors/v1_read.rs index 166f3db1e..be6853d0d 100644 --- a/server/core/src/actors/v1_read.rs +++ b/server/core/src/actors/v1_read.rs @@ -117,7 +117,7 @@ impl QueryServerReadV1 { // the credentials provided is sufficient to say if someone is // "authenticated" or not. let ct = duration_from_epoch_now(); - let mut idm_auth = self.idms.auth_async().await; + let mut idm_auth = self.idms.auth().await; security_info!(?sessionid, ?req, "Begin auth event"); // Destructure it. @@ -840,7 +840,7 @@ impl QueryServerReadV1 { eventid: Uuid, ) -> Result, OperationError> { let ct = duration_from_epoch_now(); - let mut idm_auth = self.idms.auth_async().await; + let mut idm_auth = self.idms.auth().await; // resolve the id let ident = idm_auth .validate_and_parse_token_to_ident(uat.as_deref(), ct) @@ -980,7 +980,7 @@ impl QueryServerReadV1 { eventid: Uuid, ) -> Result { let ct = duration_from_epoch_now(); - let idms_cred_update = self.idms.cred_update_transaction_async().await; + let idms_cred_update = self.idms.cred_update_transaction().await; let session_token = CredentialUpdateSessionToken { token_enc: session_token.token, }; @@ -1009,7 +1009,7 @@ impl QueryServerReadV1 { eventid: Uuid, ) -> Result { let ct = duration_from_epoch_now(); - let idms_cred_update = self.idms.cred_update_transaction_async().await; + let idms_cred_update = self.idms.cred_update_transaction().await; let session_token = CredentialUpdateSessionToken { token_enc: session_token.token, }; diff --git a/server/core/src/lib.rs b/server/core/src/lib.rs index 20a20067f..0406a6f17 100644 --- a/server/core/src/lib.rs +++ b/server/core/src/lib.rs @@ -119,7 +119,7 @@ async fn setup_qs_idms( // We generate a SINGLE idms only! - let (idms, idms_delayed) = IdmServer::new(query_server.clone(), &config.origin)?; + let (idms, idms_delayed) = IdmServer::new(query_server.clone(), &config.origin).await?; Ok((query_server, idms, idms_delayed)) } @@ -696,7 +696,7 @@ pub async fn create_server_core( None => {} } - let ldap = match LdapServer::new(&idms) { + let ldap = match LdapServer::new(&idms).await { Ok(l) => l, Err(e) => { error!("Unable to start LdapServer -> {:?}", e); diff --git a/server/lib/Cargo.toml b/server/lib/Cargo.toml index 62a27c8fa..830ed8602 100644 --- a/server/lib/Cargo.toml +++ b/server/lib/Cargo.toml @@ -20,7 +20,6 @@ name = "scaling_10k" harness = false [dependencies] -async-std.workspace = true async-trait.workspace = true base64.workspace = true base64urlsafedata.workspace = true diff --git a/server/lib/src/be/idl_sqlite.rs b/server/lib/src/be/idl_sqlite.rs index 9c12b6426..ccd1e7cc5 100644 --- a/server/lib/src/be/idl_sqlite.rs +++ b/server/lib/src/be/idl_sqlite.rs @@ -1552,7 +1552,6 @@ impl IdlSqlite { pub fn read(&self) -> IdlSqliteReadTransaction { // When we make this async, this will allow us to backoff // when we miss-grabbing from the conn-pool. - // async_std::task::yield_now().await #[allow(clippy::expect_used)] let conn = self .pool diff --git a/server/lib/src/idm/account.rs b/server/lib/src/idm/account.rs index 9033f38e4..5cb982d92 100644 --- a/server/lib/src/idm/account.rs +++ b/server/lib/src/idm/account.rs @@ -683,108 +683,100 @@ impl<'a> IdmServerProxyReadTransaction<'a> { #[cfg(test)] mod tests { use crate::prelude::*; - use async_std::task; use kanidm_proto::v1::{AuthType, UiHint}; #[test] fn test_idm_account_from_anonymous() { - let anon_e = entry_str_to_account!(JSON_ANONYMOUS_V1); + let anon_e = entry_to_account!(E_ANONYMOUS_V1.clone()); debug!("{:?}", anon_e); // I think that's it? we may want to check anonymous mech ... } - #[test] - fn test_idm_account_ui_hints() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = duration_from_epoch_now(); - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + #[idm_test] + async fn test_idm_account_ui_hints(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) { + let ct = duration_from_epoch_now(); + let mut idms_prox_write = idms.proxy_write(ct).await; - let target_uuid = Uuid::new_v4(); + let target_uuid = Uuid::new_v4(); - // Create a user. So far no ui hints. - // Create a service account - let e = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("account")), - ("class", Value::new_class("person")), - ("name", Value::new_iname("testaccount")), - ("uuid", Value::Uuid(target_uuid)), - ("description", Value::new_utf8s("testaccount")), - ("displayname", Value::new_utf8s("Test Account")) - ); + // Create a user. So far no ui hints. + // Create a service account + let e = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("person")), + ("name", Value::new_iname("testaccount")), + ("uuid", Value::Uuid(target_uuid)), + ("description", Value::new_utf8s("testaccount")), + ("displayname", Value::new_utf8s("Test Account")) + ); - let ce = CreateEvent::new_internal(vec![e]); - assert!(idms_prox_write.qs_write.create(&ce).is_ok()); + let ce = CreateEvent::new_internal(vec![e]); + assert!(idms_prox_write.qs_write.create(&ce).is_ok()); - let account = idms_prox_write - .target_to_account(target_uuid) - .expect("account must exist"); - let session_id = uuid::Uuid::new_v4(); - let uat = account - .to_userauthtoken(session_id, ct, AuthType::Passkey, None) - .expect("Unable to create uat"); + let account = idms_prox_write + .target_to_account(target_uuid) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); - // Check the ui hints are as expected. - assert!(uat.ui_hints.len() == 1); - assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate)); + // Check the ui hints are as expected. + assert!(uat.ui_hints.len() == 1); + assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate)); - // Modify the user to be a posix account, ensure they get the hint. - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("testaccount"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + // Modify the user to be a posix account, ensure they get the hint. + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("testaccount"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - // Check the ui hints are as expected. - let account = idms_prox_write - .target_to_account(target_uuid) - .expect("account must exist"); - let session_id = uuid::Uuid::new_v4(); - let uat = account - .to_userauthtoken(session_id, ct, AuthType::Passkey, None) - .expect("Unable to create uat"); + // Check the ui hints are as expected. + let account = idms_prox_write + .target_to_account(target_uuid) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); - assert!(uat.ui_hints.len() == 2); - assert!(uat.ui_hints.contains(&UiHint::PosixAccount)); - assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate)); + assert!(uat.ui_hints.len() == 2); + assert!(uat.ui_hints.contains(&UiHint::PosixAccount)); + assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate)); - // Add a group with a ui hint, and then check they get the hint. - let e = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("group")), - ("name", Value::new_iname("test_uihint_group")), - ("member", Value::Refer(target_uuid)), - ("grant_ui_hint", Value::UiHint(UiHint::ExperimentalFeatures)) - ); + // Add a group with a ui hint, and then check they get the hint. + let e = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("group")), + ("name", Value::new_iname("test_uihint_group")), + ("member", Value::Refer(target_uuid)), + ("grant_ui_hint", Value::UiHint(UiHint::ExperimentalFeatures)) + ); - let ce = CreateEvent::new_internal(vec![e]); - assert!(idms_prox_write.qs_write.create(&ce).is_ok()); + let ce = CreateEvent::new_internal(vec![e]); + assert!(idms_prox_write.qs_write.create(&ce).is_ok()); - // Check the ui hints are as expected. - let account = idms_prox_write - .target_to_account(target_uuid) - .expect("account must exist"); - let session_id = uuid::Uuid::new_v4(); - let uat = account - .to_userauthtoken(session_id, ct, AuthType::Passkey, None) - .expect("Unable to create uat"); + // Check the ui hints are as expected. + let account = idms_prox_write + .target_to_account(target_uuid) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); - assert!(uat.ui_hints.len() == 3); - assert!(uat.ui_hints.contains(&UiHint::PosixAccount)); - assert!(uat.ui_hints.contains(&UiHint::ExperimentalFeatures)); - assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate)); + assert!(uat.ui_hints.len() == 3); + assert!(uat.ui_hints.contains(&UiHint::PosixAccount)); + assert!(uat.ui_hints.contains(&UiHint::ExperimentalFeatures)); + assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate)); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } } diff --git a/server/lib/src/idm/applinks.rs b/server/lib/src/idm/applinks.rs index 2742c7a7d..d0ddb17ed 100644 --- a/server/lib/src/idm/applinks.rs +++ b/server/lib/src/idm/applinks.rs @@ -70,119 +70,114 @@ impl<'a> IdmServerProxyReadTransaction<'a> { #[cfg(test)] mod tests { - // use crate::prelude::*; - use async_std::task; + use crate::prelude::*; use kanidm_proto::internal::AppLink; - #[test] - fn test_idm_applinks_list() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = duration_from_epoch_now(); - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + #[idm_test] + async fn test_idm_applinks_list(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) { + let ct = duration_from_epoch_now(); + let mut idms_prox_write = idms.proxy_write(ct).await; - // Create an RS, the user and a group.. - let usr_uuid = Uuid::new_v4(); - let grp_uuid = Uuid::new_v4(); + // Create an RS, the user and a group.. + let usr_uuid = Uuid::new_v4(); + let grp_uuid = Uuid::new_v4(); - let e_rs: Entry = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("oauth2_resource_server")), - ("class", Value::new_class("oauth2_resource_server_basic")), - ("oauth2_rs_name", Value::new_iname("test_resource_server")), - ("displayname", Value::new_utf8s("test_resource_server")), - ( - "oauth2_rs_origin", - Value::new_url_s("https://demo.example.com").unwrap() - ), - ( - "oauth2_rs_origin_landing", - Value::new_url_s("https://demo.example.com/landing").unwrap() - ), - // System admins - ( - "oauth2_rs_scope_map", - Value::new_oauthscopemap(grp_uuid, btreeset!["read".to_string()]) - .expect("invalid oauthscope") - ) - ); + let e_rs: Entry = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("oauth2_resource_server")), + ("class", Value::new_class("oauth2_resource_server_basic")), + ("oauth2_rs_name", Value::new_iname("test_resource_server")), + ("displayname", Value::new_utf8s("test_resource_server")), + ( + "oauth2_rs_origin", + Value::new_url_s("https://demo.example.com").unwrap() + ), + ( + "oauth2_rs_origin_landing", + Value::new_url_s("https://demo.example.com/landing").unwrap() + ), + // System admins + ( + "oauth2_rs_scope_map", + Value::new_oauthscopemap(grp_uuid, btreeset!["read".to_string()]) + .expect("invalid oauthscope") + ) + ); - let e_usr = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("account")), - ("class", Value::new_class("person")), - ("name", Value::new_iname("testaccount")), - ("uuid", Value::Uuid(usr_uuid)), - ("description", Value::new_utf8s("testaccount")), - ("displayname", Value::new_utf8s("Test Account")) - ); + let e_usr = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("person")), + ("name", Value::new_iname("testaccount")), + ("uuid", Value::Uuid(usr_uuid)), + ("description", Value::new_utf8s("testaccount")), + ("displayname", Value::new_utf8s("Test Account")) + ); - let e_grp = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("group")), - ("uuid", Value::Uuid(grp_uuid)), - ("name", Value::new_iname("test_oauth2_group")) - ); + let e_grp = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("group")), + ("uuid", Value::Uuid(grp_uuid)), + ("name", Value::new_iname("test_oauth2_group")) + ); - let ce = CreateEvent::new_internal(vec![e_rs, e_grp, e_usr]); - assert!(idms_prox_write.qs_write.create(&ce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + let ce = CreateEvent::new_internal(vec![e_rs, e_grp, e_usr]); + assert!(idms_prox_write.qs_write.create(&ce).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Now do an applink query, they will not be there. - let mut idms_prox_read = task::block_on(idms.proxy_read()); + // Now do an applink query, they will not be there. + let mut idms_prox_read = idms.proxy_read().await; - let ident = idms_prox_read - .qs_read - .internal_search_uuid(usr_uuid) - .map(Identity::from_impersonate_entry_readonly) - .expect("Failed to impersonate identity"); + let ident = idms_prox_read + .qs_read + .internal_search_uuid(usr_uuid) + .map(Identity::from_impersonate_entry_readonly) + .expect("Failed to impersonate identity"); - let apps = idms_prox_read - .list_applinks(&ident) - .expect("Failed to access related apps"); + let apps = idms_prox_read + .list_applinks(&ident) + .expect("Failed to access related apps"); - assert!(apps.is_empty()); - drop(idms_prox_read); + assert!(apps.is_empty()); + drop(idms_prox_read); - // Add them to the group. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let me_inv_m = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("uuid", PartialValue::Refer(grp_uuid))), - ModifyList::new_append("member", Value::Refer(usr_uuid)), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + // Add them to the group. + let mut idms_prox_write = idms.proxy_write(ct).await; + let me_inv_m = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("uuid", PartialValue::Refer(grp_uuid))), + ModifyList::new_append("member", Value::Refer(usr_uuid)), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - let ident = idms_prox_read - .qs_read - .internal_search_uuid(usr_uuid) - .map(Identity::from_impersonate_entry_readonly) - .expect("Failed to impersonate identity"); + let ident = idms_prox_read + .qs_read + .internal_search_uuid(usr_uuid) + .map(Identity::from_impersonate_entry_readonly) + .expect("Failed to impersonate identity"); - let apps = idms_prox_read - .list_applinks(&ident) - .expect("Failed to access related apps"); + let apps = idms_prox_read + .list_applinks(&ident) + .expect("Failed to access related apps"); - let app = apps.get(0).expect("No apps return!"); + let app = apps.get(0).expect("No apps return!"); - assert!(match app { - AppLink::Oauth2 { - name, - display_name, - redirect_url, - icon, - } => { - name == "test_resource_server" - && display_name == "test_resource_server" - && redirect_url == &Url::parse("https://demo.example.com/landing").unwrap() - && icon.is_none() - } // _ => false, - }) + assert!(match app { + AppLink::Oauth2 { + name, + display_name, + redirect_url, + icon, + } => { + name == "test_resource_server" + && display_name == "test_resource_server" + && redirect_url == &Url::parse("https://demo.example.com/landing").unwrap() + && icon.is_none() + } // _ => false, }) } } diff --git a/server/lib/src/idm/authsession.rs b/server/lib/src/idm/authsession.rs index 97398432d..98667ac21 100644 --- a/server/lib/src/idm/authsession.rs +++ b/server/lib/src/idm/authsession.rs @@ -1037,7 +1037,7 @@ mod tests { let webauthn = create_webauthn(); - let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1); + let anon_account = entry_to_account!(E_ANONYMOUS_V1.clone()); let (session, state) = AuthSession::new( anon_account, @@ -1107,7 +1107,7 @@ mod tests { sketching::test_init(); let webauthn = create_webauthn(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1.clone()); // manually load in a cred let p = CryptoPolicy::minimum(); let cred = Credential::new_password_only(&p, "test_password").unwrap(); @@ -1166,7 +1166,7 @@ mod tests { let jws_signer = create_jwt_signer(); let webauthn = create_webauthn(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1.clone()); // manually load in a cred let p = CryptoPolicy::minimum(); let cred = Credential::new_password_only(&p, "list@no3IBTyqHu$bad").unwrap(); @@ -1258,7 +1258,7 @@ mod tests { let webauthn = create_webauthn(); let jws_signer = create_jwt_signer(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1); // Setup a fake time stamp for consistency. let ts = Duration::from_secs(12345); @@ -1418,7 +1418,7 @@ mod tests { let webauthn = create_webauthn(); let jws_signer = create_jwt_signer(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1); // Setup a fake time stamp for consistency. let ts = Duration::from_secs(12345); @@ -1582,7 +1582,7 @@ mod tests { let (async_tx, mut async_rx) = unbounded(); let ts = duration_from_epoch_now(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1.clone()); let (webauthn, mut wa, wan_cred) = setup_webauthn_passkey(account.name.as_str()); let jws_signer = create_jwt_signer(); @@ -1719,7 +1719,7 @@ mod tests { let (async_tx, mut async_rx) = unbounded(); let ts = duration_from_epoch_now(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1); let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.name.as_str()); let jws_signer = create_jwt_signer(); @@ -1896,7 +1896,7 @@ mod tests { let (async_tx, mut async_rx) = unbounded(); let ts = duration_from_epoch_now(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1); let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.name.as_str()); let jws_signer = create_jwt_signer(); @@ -2145,7 +2145,7 @@ mod tests { let jws_signer = create_jwt_signer(); let webauthn = create_webauthn(); // create the ent - let mut account = entry_str_to_account!(JSON_ADMIN_V1); + let mut account = entry_to_account!(E_ADMIN_V1); // Setup a fake time stamp for consistency. let ts = Duration::from_secs(12345); diff --git a/server/lib/src/idm/credupdatesession.rs b/server/lib/src/idm/credupdatesession.rs index e7db7c7a9..3014e48a3 100644 --- a/server/lib/src/idm/credupdatesession.rs +++ b/server/lib/src/idm/credupdatesession.rs @@ -1741,7 +1741,7 @@ mod tests { pw: &str, ct: Duration, ) -> Option { - let mut idms_auth = idms.auth(); + let mut idms_auth = idms.auth().await; let auth_init = AuthEvent::named_init("testperson"); @@ -1800,7 +1800,7 @@ mod tests { token: &Totp, ct: Duration, ) -> Option { - let mut idms_auth = idms.auth(); + let mut idms_auth = idms.auth().await; let auth_init = AuthEvent::named_init("testperson"); @@ -1873,7 +1873,7 @@ mod tests { code: &str, ct: Duration, ) -> Option { - let mut idms_auth = idms.auth(); + let mut idms_auth = idms.auth().await; let auth_init = AuthEvent::named_init("testperson"); @@ -1948,7 +1948,7 @@ mod tests { origin: Url, ct: Duration, ) -> Option { - let mut idms_auth = idms.auth(); + let mut idms_auth = idms.auth().await; let auth_init = AuthEvent::named_init("testperson"); @@ -2027,7 +2027,7 @@ mod tests { let ct = Duration::from_secs(TEST_CURRENT_TIME); let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction_async().await; + let cutxn = idms.cred_update_transaction().await; // The session exists let c_status = cutxn.credential_update_status(&cust, ct); assert!(c_status.is_ok()); @@ -2037,7 +2037,7 @@ mod tests { 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().await; // Now fake going back in time .... allows the tokne to decrypt, but the session // is gone anyway! @@ -2057,7 +2057,7 @@ mod tests { let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; // Get the credential status - this should tell // us the details of the credentials, as well as @@ -2088,7 +2088,7 @@ mod tests { // Test deleting the pw let (cust, _) = renew_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; let c_status = cutxn .credential_update_status(&cust, ct) @@ -2125,7 +2125,7 @@ mod tests { let ct = Duration::from_secs(TEST_CURRENT_TIME); let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; // Setup the PW let c_status = cutxn @@ -2188,7 +2188,7 @@ mod tests { // If we remove TOTP, show it reverts back. let (cust, _) = renew_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; let c_status = cutxn .credential_primary_remove_totp(&cust, ct, "totp") @@ -2219,7 +2219,7 @@ mod tests { let ct = Duration::from_secs(TEST_CURRENT_TIME); let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; // Setup the PW let c_status = cutxn @@ -2293,7 +2293,7 @@ mod tests { let ct = Duration::from_secs(TEST_CURRENT_TIME); let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; // Setup the PW let _c_status = cutxn @@ -2369,7 +2369,7 @@ mod tests { // Renew to start the next steps let (cust, _) = renew_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; // Only 7 codes left. let c_status = cutxn @@ -2430,7 +2430,7 @@ mod tests { let ct = Duration::from_secs(TEST_CURRENT_TIME); let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; // Setup the PW let c_status = cutxn @@ -2482,7 +2482,7 @@ mod tests { let ct = Duration::from_secs(TEST_CURRENT_TIME); let (cust, _) = setup_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; let origin = cutxn.get_origin().clone(); // Create a soft passkey @@ -2538,7 +2538,7 @@ mod tests { // Now test removing the token let (cust, _) = renew_test_session(idms, ct).await; - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; trace!(?c_status); assert!(c_status.primary.is_none()); diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index eff3082f4..50c985479 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -4,7 +4,6 @@ use std::collections::BTreeSet; use std::iter; -use async_std::task; use kanidm_proto::v1::{ApiToken, OperationError, UserAuthToken}; use ldap3_proto::simple::*; use regex::Regex; @@ -59,9 +58,9 @@ pub struct LdapServer { } impl LdapServer { - pub fn new(idms: &IdmServer) -> Result { + pub async fn new(idms: &IdmServer) -> Result { // let ct = duration_from_epoch_now(); - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; // This is the rootdse path. // get the domain_info item let domain_entry = idms_prox_read @@ -378,7 +377,7 @@ impl LdapServer { ); let ct = duration_from_epoch_now(); - let mut idm_auth = idms.auth_async().await; + let mut idm_auth = idms.auth().await; let target_uuid: Uuid = if dn.is_empty() { if pw.is_empty() { @@ -603,7 +602,6 @@ mod tests { use crate::prelude::*; use std::str::FromStr; - use async_std::task; use compact_jwt::{Jws, JwsUnverified}; use hashbrown::HashSet; use kanidm_proto::v1::ApiToken; @@ -616,152 +614,160 @@ mod tests { const TEST_PASSWORD: &str = "ntaoeuntnaoeuhraohuercahušŸ˜"; - #[test] - fn test_ldap_simple_bind() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let ldaps = LdapServer::new(idms).expect("failed to start ldap"); + #[idm_test] + async fn test_ldap_simple_bind(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + let ldaps = LdapServer::new(idms).await.expect("failed to start ldap"); - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - // make the admin a valid posix account - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + // make the admin a valid posix account + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - let anon_t = task::block_on(ldaps.do_bind(idms, "", "")) - .unwrap() - .unwrap(); - assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); - assert!( - task::block_on(ldaps.do_bind(idms, "", "test")).unwrap_err() - == OperationError::NotAuthenticated - ); + let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap(); + assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); + assert!( + ldaps.do_bind(idms, "", "test").await.unwrap_err() == OperationError::NotAuthenticated + ); - // Now test the admin and various DN's - let admin_t = task::block_on(ldaps.do_bind(idms, "admin", TEST_PASSWORD)) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = - task::block_on(ldaps.do_bind(idms, "admin@example.com", TEST_PASSWORD)) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind(idms, STR_UUID_ADMIN, TEST_PASSWORD)) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind( - idms, - "name=admin,dc=example,dc=com", - TEST_PASSWORD, - )) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind( - idms, - "spn=admin@example.com,dc=example,dc=com", - TEST_PASSWORD, - )) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind( - idms, - format!("uuid={STR_UUID_ADMIN},dc=example,dc=com").as_str(), - TEST_PASSWORD, - )) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + // Now test the admin and various DN's + let admin_t = ldaps + .do_bind(idms, "admin", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "admin@example.com", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, STR_UUID_ADMIN, TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "name=admin,dc=example,dc=com", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind( + idms, + "spn=admin@example.com,dc=example,dc=com", + TEST_PASSWORD, + ) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind( + idms, + format!("uuid={STR_UUID_ADMIN},dc=example,dc=com").as_str(), + TEST_PASSWORD, + ) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind(idms, "name=admin", TEST_PASSWORD)) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = - task::block_on(ldaps.do_bind(idms, "spn=admin@example.com", TEST_PASSWORD)) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind( - idms, - format!("uuid={STR_UUID_ADMIN}").as_str(), - TEST_PASSWORD, - )) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "name=admin", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "spn=admin@example.com", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind( + idms, + format!("uuid={STR_UUID_ADMIN}").as_str(), + TEST_PASSWORD, + ) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = - task::block_on(ldaps.do_bind(idms, "admin,dc=example,dc=com", TEST_PASSWORD)) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind( - idms, - "admin@example.com,dc=example,dc=com", - TEST_PASSWORD, - )) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - let admin_t = task::block_on(ldaps.do_bind( - idms, - format!("{STR_UUID_ADMIN},dc=example,dc=com").as_str(), - TEST_PASSWORD, - )) - .unwrap() - .unwrap(); - assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "admin,dc=example,dc=com", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "admin@example.com,dc=example,dc=com", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind( + idms, + format!("{STR_UUID_ADMIN},dc=example,dc=com").as_str(), + TEST_PASSWORD, + ) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); - // Bad password, check last to prevent softlocking of the admin account. - assert!(task::block_on(ldaps.do_bind(idms, "admin", "test")) - .unwrap() - .is_none()); + // Bad password, check last to prevent softlocking of the admin account. + assert!(ldaps + .do_bind(idms, "admin", "test") + .await + .unwrap() + .is_none()); - // Non-existent and invalid DNs - assert!(task::block_on(ldaps.do_bind( - idms, - "spn=admin@example.com,dc=clownshoes,dc=example,dc=com", - TEST_PASSWORD - )) - .is_err()); - assert!(task::block_on(ldaps.do_bind( - idms, - "spn=claire@example.com,dc=example,dc=com", - TEST_PASSWORD - )) - .is_err()); - assert!( - task::block_on(ldaps.do_bind(idms, ",dc=example,dc=com", TEST_PASSWORD)) - .is_err() - ); - assert!( - task::block_on(ldaps.do_bind(idms, "dc=example,dc=com", TEST_PASSWORD)) - .is_err() - ); + // Non-existent and invalid DNs + assert!(ldaps + .do_bind( + idms, + "spn=admin@example.com,dc=clownshoes,dc=example,dc=com", + TEST_PASSWORD + ) + .await + .is_err()); + assert!(ldaps + .do_bind( + idms, + "spn=claire@example.com,dc=example,dc=com", + TEST_PASSWORD + ) + .await + .is_err()); + assert!(ldaps + .do_bind(idms, ",dc=example,dc=com", TEST_PASSWORD) + .await + .is_err()); + assert!(ldaps + .do_bind(idms, "dc=example,dc=com", TEST_PASSWORD) + .await + .is_err()); - assert!(task::block_on(ldaps.do_bind(idms, "claire", "test")).is_err()); - } - ) + assert!(ldaps.do_bind(idms, "claire", "test").await.is_err()); } macro_rules! assert_entry_contains { @@ -789,384 +795,370 @@ mod tests { }}; } - #[test] - fn test_ldap_virtual_attribute_generation() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let ldaps = LdapServer::new(idms).expect("failed to start ldap"); + #[idm_test] + async fn test_ldap_virtual_attribute_generation( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + let ldaps = LdapServer::new(idms).await.expect("failed to start ldap"); - let ssh_ed25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst"; + let ssh_ed25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst"; - // Setup a user we want to check. - { - let e1 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("person")), - ("class", Value::new_class("account")), - ("class", Value::new_class("posixaccount")), - ("name", Value::new_iname("testperson1")), - ( - "uuid", - Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")) - ), - ("description", Value::new_utf8s("testperson1")), - ("displayname", Value::new_utf8s("testperson1")), - ("gidnumber", Value::new_uint32(12345678)), - ("loginshell", Value::new_iutf8("/bin/zsh")), - ("ssh_publickey", Value::new_sshkey_str("test", ssh_ed25519)) - ); + // Setup a user we want to check. + { + let e1 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("person")), + ("class", Value::new_class("account")), + ("class", Value::new_class("posixaccount")), + ("name", Value::new_iname("testperson1")), + ( + "uuid", + Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")) + ), + ("description", Value::new_utf8s("testperson1")), + ("displayname", Value::new_utf8s("testperson1")), + ("gidnumber", Value::new_uint32(12345678)), + ("loginshell", Value::new_iutf8("/bin/zsh")), + ("ssh_publickey", Value::new_sshkey_str("test", ssh_ed25519)) + ); - let mut server_txn = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let ce = CreateEvent::new_internal(vec![e1]); - assert!(server_txn - .qs_write - .create(&ce) - .and_then(|_| server_txn.commit()) - .is_ok()); - } + let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await; + let ce = CreateEvent::new_internal(vec![e1]); + assert!(server_txn + .qs_write + .create(&ce) + .and_then(|_| server_txn.commit()) + .is_ok()); + } - // Setup the anonymous login. - let anon_t = task::block_on(ldaps.do_bind(idms, "", "")) - .unwrap() - .unwrap(); - assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); + // Setup the anonymous login. + let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap(); + assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); - // Check that when we request *, we get default list. - let sr = SearchRequest { - msgid: 1, - base: "dc=example,dc=com".to_string(), - scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), - attrs: vec!["*".to_string()], - }; - let r1 = task::block_on(ldaps.do_search(idms, &sr, &anon_t)).unwrap(); + // Check that when we request *, we get default list. + let sr = SearchRequest { + msgid: 1, + base: "dc=example,dc=com".to_string(), + scope: LdapSearchScope::Subtree, + filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + attrs: vec!["*".to_string()], + }; + let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); - // The result, and the ldap proto success msg. - assert!(r1.len() == 2); - match &r1[0].op { - LdapOp::SearchResultEntry(lsre) => { - assert_entry_contains!( - lsre, - "spn=testperson1@example.com,dc=example,dc=com", - ("class", "object"), - ("class", "person"), - ("class", "account"), - ("class", "posixaccount"), - ("displayname", "testperson1"), - ("name", "testperson1"), - ("gidnumber", "12345678"), - ("loginshell", "/bin/zsh"), - ("ssh_publickey", ssh_ed25519), - ("uuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930") - ); - } - _ => assert!(false), - }; - - // Check that when we request +, we get all attrs and the vattrs - let sr = SearchRequest { - msgid: 1, - base: "dc=example,dc=com".to_string(), - scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), - attrs: vec!["+".to_string()], - }; - let r1 = task::block_on(ldaps.do_search(idms, &sr, &anon_t)).unwrap(); - - // The result, and the ldap proto success msg. - assert!(r1.len() == 2); - match &r1[0].op { - LdapOp::SearchResultEntry(lsre) => { - assert_entry_contains!( - lsre, - "spn=testperson1@example.com,dc=example,dc=com", - ("objectclass", "object"), - ("objectclass", "person"), - ("objectclass", "account"), - ("objectclass", "posixaccount"), - ("displayname", "testperson1"), - ("name", "testperson1"), - ("gidnumber", "12345678"), - ("loginshell", "/bin/zsh"), - ("ssh_publickey", ssh_ed25519), - ("entryuuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930"), - ("entrydn", "spn=testperson1@example.com,dc=example,dc=com"), - ("uidnumber", "12345678"), - ("cn", "testperson1"), - ("keys", ssh_ed25519) - ); - } - _ => assert!(false), - }; - - // Check that when we request an attr by name, we get all of them correctly. - let sr = SearchRequest { - msgid: 1, - base: "dc=example,dc=com".to_string(), - scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), - attrs: vec![ - "name".to_string(), - "entrydn".to_string(), - "keys".to_string(), - "uidnumber".to_string(), - ], - }; - let r1 = task::block_on(ldaps.do_search(idms, &sr, &anon_t)).unwrap(); - - // The result, and the ldap proto success msg. - assert!(r1.len() == 2); - match &r1[0].op { - LdapOp::SearchResultEntry(lsre) => { - assert_entry_contains!( - lsre, - "spn=testperson1@example.com,dc=example,dc=com", - ("name", "testperson1"), - ("entrydn", "spn=testperson1@example.com,dc=example,dc=com"), - ("uidnumber", "12345678"), - ("keys", ssh_ed25519) - ); - } - _ => assert!(false), - }; + // The result, and the ldap proto success msg. + assert!(r1.len() == 2); + match &r1[0].op { + LdapOp::SearchResultEntry(lsre) => { + assert_entry_contains!( + lsre, + "spn=testperson1@example.com,dc=example,dc=com", + ("class", "object"), + ("class", "person"), + ("class", "account"), + ("class", "posixaccount"), + ("displayname", "testperson1"), + ("name", "testperson1"), + ("gidnumber", "12345678"), + ("loginshell", "/bin/zsh"), + ("ssh_publickey", ssh_ed25519), + ("uuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930") + ); } - ) + _ => assert!(false), + }; + + // Check that when we request +, we get all attrs and the vattrs + let sr = SearchRequest { + msgid: 1, + base: "dc=example,dc=com".to_string(), + scope: LdapSearchScope::Subtree, + filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + attrs: vec!["+".to_string()], + }; + let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); + + // The result, and the ldap proto success msg. + assert!(r1.len() == 2); + match &r1[0].op { + LdapOp::SearchResultEntry(lsre) => { + assert_entry_contains!( + lsre, + "spn=testperson1@example.com,dc=example,dc=com", + ("objectclass", "object"), + ("objectclass", "person"), + ("objectclass", "account"), + ("objectclass", "posixaccount"), + ("displayname", "testperson1"), + ("name", "testperson1"), + ("gidnumber", "12345678"), + ("loginshell", "/bin/zsh"), + ("ssh_publickey", ssh_ed25519), + ("entryuuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930"), + ("entrydn", "spn=testperson1@example.com,dc=example,dc=com"), + ("uidnumber", "12345678"), + ("cn", "testperson1"), + ("keys", ssh_ed25519) + ); + } + _ => assert!(false), + }; + + // Check that when we request an attr by name, we get all of them correctly. + let sr = SearchRequest { + msgid: 1, + base: "dc=example,dc=com".to_string(), + scope: LdapSearchScope::Subtree, + filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + attrs: vec![ + "name".to_string(), + "entrydn".to_string(), + "keys".to_string(), + "uidnumber".to_string(), + ], + }; + let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); + + // The result, and the ldap proto success msg. + assert!(r1.len() == 2); + match &r1[0].op { + LdapOp::SearchResultEntry(lsre) => { + assert_entry_contains!( + lsre, + "spn=testperson1@example.com,dc=example,dc=com", + ("name", "testperson1"), + ("entrydn", "spn=testperson1@example.com,dc=example,dc=com"), + ("uidnumber", "12345678"), + ("keys", ssh_ed25519) + ); + } + _ => assert!(false), + }; } - #[test] - fn test_ldap_token_privilege_granting() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - // Setup the ldap server - let ldaps = LdapServer::new(idms).expect("failed to start ldap"); + #[idm_test] + async fn test_ldap_token_privilege_granting( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + // Setup the ldap server + let ldaps = LdapServer::new(idms).await.expect("failed to start ldap"); - // Prebuild the search req we'll be using this test. - let sr = SearchRequest { - msgid: 1, - base: "dc=example,dc=com".to_string(), - scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), - attrs: vec![ - "name".to_string(), - "mail".to_string(), - "mail;primary".to_string(), - "mail;alternative".to_string(), - "emailprimary".to_string(), - "emailalternative".to_string(), - ], - }; + // Prebuild the search req we'll be using this test. + let sr = SearchRequest { + msgid: 1, + base: "dc=example,dc=com".to_string(), + scope: LdapSearchScope::Subtree, + filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + attrs: vec![ + "name".to_string(), + "mail".to_string(), + "mail;primary".to_string(), + "mail;alternative".to_string(), + "emailprimary".to_string(), + "emailalternative".to_string(), + ], + }; - let sa_uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); + let sa_uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); - // Configure the user account that will have the tokens issued. - // Should be a SERVICE account. - let apitoken = { - // Create a service account, + // Configure the user account that will have the tokens issued. + // Should be a SERVICE account. + let apitoken = { + // Create a service account, - let e1 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("service_account")), - ("class", Value::new_class("account")), - ("uuid", Value::Uuid(sa_uuid)), - ("name", Value::new_iname("service_permission_test")), - ("displayname", Value::new_utf8s("service_permission_test")) - ); + let e1 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("service_account")), + ("class", Value::new_class("account")), + ("uuid", Value::Uuid(sa_uuid)), + ("name", Value::new_iname("service_permission_test")), + ("displayname", Value::new_utf8s("service_permission_test")) + ); - // Setup a person with an email - let e2 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("person")), - ("class", Value::new_class("account")), - ("class", Value::new_class("posixaccount")), - ("name", Value::new_iname("testperson1")), - ( - "mail", - Value::EmailAddress("testperson1@example.com".to_string(), true) - ), - ( - "mail", - Value::EmailAddress( - "testperson1.alternative@example.com".to_string(), - false - ) - ), - ("description", Value::new_utf8s("testperson1")), - ("displayname", Value::new_utf8s("testperson1")), - ("gidnumber", Value::new_uint32(12345678)), - ("loginshell", Value::new_iutf8("/bin/zsh")) - ); + // Setup a person with an email + let e2 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("person")), + ("class", Value::new_class("account")), + ("class", Value::new_class("posixaccount")), + ("name", Value::new_iname("testperson1")), + ( + "mail", + Value::EmailAddress("testperson1@example.com".to_string(), true) + ), + ( + "mail", + Value::EmailAddress("testperson1.alternative@example.com".to_string(), false) + ), + ("description", Value::new_utf8s("testperson1")), + ("displayname", Value::new_utf8s("testperson1")), + ("gidnumber", Value::new_uint32(12345678)), + ("loginshell", Value::new_iutf8("/bin/zsh")) + ); - // Setup an access control for the service account to view mail attrs. + // Setup an access control for the service account to view mail attrs. - let ct = duration_from_epoch_now(); + let ct = duration_from_epoch_now(); - let mut server_txn = task::block_on(idms.proxy_write(ct)); - let ce = CreateEvent::new_internal(vec![e1, e2]); - assert!(server_txn.qs_write.create(&ce).is_ok()); + let mut server_txn = idms.proxy_write(ct).await; + let ce = CreateEvent::new_internal(vec![e1, e2]); + assert!(server_txn.qs_write.create(&ce).is_ok()); - // idm_people_read_priv - let me = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq( - "name", - PartialValue::new_iname("idm_people_read_priv") - )), - ModifyList::new_list(vec![Modify::Present( - AttrString::from("member"), - Value::Refer(sa_uuid), - )]), - ) - }; - assert!(server_txn.qs_write.modify(&me).is_ok()); + // idm_people_read_priv + let me = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq( + "name", + PartialValue::new_iname("idm_people_read_priv") + )), + ModifyList::new_list(vec![Modify::Present( + AttrString::from("member"), + Value::Refer(sa_uuid), + )]), + ) + }; + assert!(server_txn.qs_write.modify(&me).is_ok()); - // Issue a token - // make it purpose = ldap <- currently purpose isn't supported, - // it's an idea for future. - let gte = GenerateApiTokenEvent::new_internal(sa_uuid, "TestToken", None); + // Issue a token + // make it purpose = ldap <- currently purpose isn't supported, + // it's an idea for future. + let gte = GenerateApiTokenEvent::new_internal(sa_uuid, "TestToken", None); - let apitoken = server_txn - .service_account_generate_api_token(>e, ct) - .expect("Failed to create new apitoken"); + let apitoken = server_txn + .service_account_generate_api_token(>e, ct) + .expect("Failed to create new apitoken"); - assert!(server_txn.commit().is_ok()); + assert!(server_txn.commit().is_ok()); - apitoken - }; + apitoken + }; - // assert the token fails on non-ldap events token-xchg <- currently - // we don't have purpose so this isn't tested. + // assert the token fails on non-ldap events token-xchg <- currently + // we don't have purpose so this isn't tested. - // Bind with anonymous, search and show mail attr isn't accessible. - let anon_lbt = task::block_on(ldaps.do_bind(idms, "", "")) - .unwrap() - .unwrap(); - assert!(anon_lbt.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); + // Bind with anonymous, search and show mail attr isn't accessible. + let anon_lbt = ldaps.do_bind(idms, "", "").await.unwrap().unwrap(); + assert!(anon_lbt.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); - let r1 = task::block_on(ldaps.do_search(idms, &sr, &anon_lbt)).unwrap(); - assert!(r1.len() == 2); - match &r1[0].op { - LdapOp::SearchResultEntry(lsre) => { - assert_entry_contains!( - lsre, - "spn=testperson1@example.com,dc=example,dc=com", - ("name", "testperson1") - ); - } - _ => assert!(false), - }; - - // Inspect the token to get its uuid out. - let apitoken_unverified = - JwsUnverified::from_str(&apitoken).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(); - - // Bind using the token as a DN - let sa_lbt = task::block_on(ldaps.do_bind(idms, "dn=token", &apitoken)) - .unwrap() - .unwrap(); - assert!(sa_lbt.effective_session == LdapSession::ApiToken(apitoken_inner.clone())); - - // Bind using the token as a pw - let sa_lbt = task::block_on(ldaps.do_bind(idms, "", &apitoken)) - .unwrap() - .unwrap(); - assert!(sa_lbt.effective_session == LdapSession::ApiToken(apitoken_inner)); - - // Search and retrieve mail that's now accessible. - let r1 = task::block_on(ldaps.do_search(idms, &sr, &sa_lbt)).unwrap(); - assert!(r1.len() == 2); - match &r1[0].op { - LdapOp::SearchResultEntry(lsre) => { - assert_entry_contains!( - lsre, - "spn=testperson1@example.com,dc=example,dc=com", - ("name", "testperson1"), - ("mail", "testperson1@example.com"), - ("mail", "testperson1.alternative@example.com"), - ("mail;primary", "testperson1@example.com"), - ("mail;alternative", "testperson1.alternative@example.com"), - ("emailprimary", "testperson1@example.com"), - ("emailalternative", "testperson1.alternative@example.com") - ); - } - _ => assert!(false), - }; + let r1 = ldaps.do_search(idms, &sr, &anon_lbt).await.unwrap(); + assert!(r1.len() == 2); + match &r1[0].op { + LdapOp::SearchResultEntry(lsre) => { + assert_entry_contains!( + lsre, + "spn=testperson1@example.com,dc=example,dc=com", + ("name", "testperson1") + ); } - ) + _ => assert!(false), + }; + + // Inspect the token to get its uuid out. + let apitoken_unverified = + JwsUnverified::from_str(&apitoken).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(); + + // Bind using the token as a DN + let sa_lbt = ldaps + .do_bind(idms, "dn=token", &apitoken) + .await + .unwrap() + .unwrap(); + assert!(sa_lbt.effective_session == LdapSession::ApiToken(apitoken_inner.clone())); + + // Bind using the token as a pw + let sa_lbt = ldaps.do_bind(idms, "", &apitoken).await.unwrap().unwrap(); + assert!(sa_lbt.effective_session == LdapSession::ApiToken(apitoken_inner)); + + // Search and retrieve mail that's now accessible. + let r1 = ldaps.do_search(idms, &sr, &sa_lbt).await.unwrap(); + assert!(r1.len() == 2); + match &r1[0].op { + LdapOp::SearchResultEntry(lsre) => { + assert_entry_contains!( + lsre, + "spn=testperson1@example.com,dc=example,dc=com", + ("name", "testperson1"), + ("mail", "testperson1@example.com"), + ("mail", "testperson1.alternative@example.com"), + ("mail;primary", "testperson1@example.com"), + ("mail;alternative", "testperson1.alternative@example.com"), + ("emailprimary", "testperson1@example.com"), + ("emailalternative", "testperson1.alternative@example.com") + ); + } + _ => assert!(false), + }; } - #[test] - fn test_ldap_virtual_attribute_with_all_attr_search() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let ldaps = LdapServer::new(idms).expect("failed to start ldap"); + #[idm_test] + async fn test_ldap_virtual_attribute_with_all_attr_search( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + let ldaps = LdapServer::new(idms).await.expect("failed to start ldap"); - let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); + let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); - // Setup a user we want to check. - { - let e1 = entry_init!( - ("class", Value::new_class("person")), - ("class", Value::new_class("account")), - ("name", Value::new_iname("testperson1")), - ("uuid", Value::Uuid(acct_uuid)), - ("description", Value::new_utf8s("testperson1")), - ("displayname", Value::new_utf8s("testperson1")) - ); + // Setup a user we want to check. + { + let e1 = entry_init!( + ("class", Value::new_class("person")), + ("class", Value::new_class("account")), + ("name", Value::new_iname("testperson1")), + ("uuid", Value::Uuid(acct_uuid)), + ("description", Value::new_utf8s("testperson1")), + ("displayname", Value::new_utf8s("testperson1")) + ); - let mut server_txn = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - assert!(server_txn - .qs_write - .internal_create(vec![e1]) - .and_then(|_| server_txn.commit()) - .is_ok()); - } + let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await; + assert!(server_txn + .qs_write + .internal_create(vec![e1]) + .and_then(|_| server_txn.commit()) + .is_ok()); + } - // Setup the anonymous login. - let anon_t = task::block_on(ldaps.do_bind(idms, "", "")) - .unwrap() - .unwrap(); - assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); + // Setup the anonymous login. + let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap(); + assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); - // Check that when we request a virtual attr by name *and* all_attrs we get all the requested values. - let sr = SearchRequest { - msgid: 1, - base: "dc=example,dc=com".to_string(), - scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), - attrs: vec![ - "*".to_string(), - // Already being returned - "name".to_string(), - // This is a virtual attribute - "entryuuid".to_string(), - ], - }; - let r1 = task::block_on(ldaps.do_search(idms, &sr, &anon_t)).unwrap(); + // Check that when we request a virtual attr by name *and* all_attrs we get all the requested values. + let sr = SearchRequest { + msgid: 1, + base: "dc=example,dc=com".to_string(), + scope: LdapSearchScope::Subtree, + filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + attrs: vec![ + "*".to_string(), + // Already being returned + "name".to_string(), + // This is a virtual attribute + "entryuuid".to_string(), + ], + }; + let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); - // The result, and the ldap proto success msg. - assert!(r1.len() == 2); - match &r1[0].op { - LdapOp::SearchResultEntry(lsre) => { - assert_entry_contains!( - lsre, - "spn=testperson1@example.com,dc=example,dc=com", - ("name", "testperson1"), - ("displayname", "testperson1"), - ("uuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930"), - ("entryuuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930") - ); - } - _ => assert!(false), - }; + // The result, and the ldap proto success msg. + assert!(r1.len() == 2); + match &r1[0].op { + LdapOp::SearchResultEntry(lsre) => { + assert_entry_contains!( + lsre, + "spn=testperson1@example.com,dc=example,dc=com", + ("name", "testperson1"), + ("displayname", "testperson1"), + ("uuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930"), + ("entryuuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930") + ); } - ) + _ => assert!(false), + }; } } diff --git a/server/lib/src/idm/oauth2.rs b/server/lib/src/idm/oauth2.rs index 953513e48..48c6b95c7 100644 --- a/server/lib/src/idm/oauth2.rs +++ b/server/lib/src/idm/oauth2.rs @@ -1631,8 +1631,6 @@ mod tests { use crate::idm::server::{IdmServer, IdmServerTransaction}; use crate::prelude::*; - use async_std::task; - const TEST_CURRENT_TIME: u64 = 6000; const UAT_EXPIRE: u64 = 5; const TOKEN_EXPIRE: u64 = 900; @@ -1678,14 +1676,14 @@ mod tests { } // setup an oauth2 instance. - fn setup_oauth2_resource_server( + async fn setup_oauth2_resource_server( idms: &IdmServer, ct: Duration, enable_pkce: bool, enable_legacy_crypto: bool, prefer_short_username: bool, ) -> (String, UserAuthToken, Identity, Uuid) { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + let mut idms_prox_write = idms.proxy_write(ct).await; let uuid = Uuid::new_v4(); @@ -1766,12 +1764,12 @@ mod tests { (secret, uat, ident, uuid) } - fn setup_idm_admin( + async fn setup_idm_admin( idms: &IdmServer, ct: Duration, authtype: AuthType, ) -> (UserAuthToken, Identity) { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + let mut idms_prox_write = idms.proxy_write(ct).await; let account = idms_prox_write .target_to_account(UUID_IDM_ADMIN) .expect("account must exist"); @@ -1788,1945 +1786,1864 @@ mod tests { (uat, ident) } - #[test] - fn test_idm_oauth2_basic_function() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); + #[idm_test] + async fn test_idm_oauth2_basic_function(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - // Get an ident/uat for now. + // Get an ident/uat for now. - // == Setup the authorisation request - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + // == Setup the authorisation request + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Should be in the consent phase; - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; - - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); - - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } - - // Check we are reflecting the CSRF properly. - assert!(permit_success.state == "123"); - - // == Submit the token exchange code. - - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: Some("test_resource_server".to_string()), - client_secret: Some(secret), - // From the first step. - code_verifier, - }; - - let token_response = idms_prox_read - .check_oauth2_token_exchange(None, &token_req, ct) - .expect("Failed to perform oauth2 token exchange"); - - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(_)) => {} - _ => assert!(false), - } - - // šŸŽ‰ We got a token! In the future we can then check introspection from this point. - assert!(token_response.token_type == "bearer"); - } - ) - } - - #[test] - fn test_idm_oauth2_invalid_authorisation_requests() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - // Test invalid oauth2 authorisation states/requests. - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); - - let (anon_uat, anon_ident) = setup_idm_admin(idms, ct, AuthType::Anonymous); - let (idm_admin_uat, idm_admin_ident) = setup_idm_admin(idms, ct, AuthType::PasswordMfa); - - // Need a uat from a user not in the group. Probs anonymous. - let idms_prox_read = task::block_on(idms.proxy_read()); - - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - - let pkce_request = Some(PkceRequest { - code_challenge: Base64UrlSafeData(code_challenge), - code_challenge_method: CodeChallengeMethod::S256, - }); - - // * response type != code. - let auth_req = AuthorisationRequest { - response_type: "NOTCODE".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: pkce_request.clone(), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::UnsupportedResponseType - ); - - // * No pkce in pkce enforced mode. - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: None, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::InvalidRequest - ); - - // * invalid rs name - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "NOT A REAL RESOURCE SERVER".to_string(), - state: "123".to_string(), - pkce_request: pkce_request.clone(), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::InvalidClientId - ); - - // * mis match origin in the redirect. - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: pkce_request.clone(), - redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::InvalidOrigin - ); - - // Requested scope is not available - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: pkce_request.clone(), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "invalid_scope read".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::AccessDenied - ); - - // Not a member of the group. - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: pkce_request.clone(), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "read openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&idm_admin_ident, &idm_admin_uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::AccessDenied - ); - - // Deny Anonymous auth methods - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "read openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; - - assert!( - idms_prox_read - .check_oauth2_authorisation(&anon_ident, &anon_uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::AccessDenied - ); - }) - } - - #[test] - fn test_idm_oauth2_invalid_authorisation_permit_requests() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - // Test invalid oauth2 authorisation states/requests. - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); - - let (uat2, ident2) = { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let account = idms_prox_write - .target_to_account(UUID_IDM_ADMIN) - .expect("account must exist"); - let session_id = uuid::Uuid::new_v4(); - let uat2 = account - .to_userauthtoken( - session_id, - ct, - AuthType::PasswordMfa, - Some(AUTH_SESSION_EXPIRY), - ) - .expect("Unable to create uat"); - let ident2 = idms_prox_write - .process_uat_to_identity(&uat2, ct) - .expect("Unable to process uat"); - (uat2, ident2) - }; - - let idms_prox_read = task::block_on(idms.proxy_read()); - - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); - - let consent_token = if let AuthoriseResponse::ConsentRequested { - consent_token, .. - } = consent_request - { + // Should be in the consent phase; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { consent_token } else { unreachable!(); }; - // Invalid permits - // * expired token, aka past ttl. - assert!( - idms_prox_read - .check_oauth2_authorise_permit( - &ident, - &uat, - &consent_token, - ct + Duration::from_secs(TOKEN_EXPIRE), - ) - .unwrap_err() - == OperationError::CryptographyError - ); + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - // * incorrect ident - // We get another uat, but for a different user, and we'll introduce these - // inconsistently to cause confusion. + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - assert!( - idms_prox_read - .check_oauth2_authorise_permit(&ident2, &uat, &consent_token, ct,) - .unwrap_err() - == OperationError::InvalidSessionState - ); + // Check we are reflecting the CSRF properly. + assert!(permit_success.state == "123"); - // * incorrect session id - assert!( - idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat2, &consent_token, ct,) - .unwrap_err() - == OperationError::InvalidSessionState - ); - }) + // == Submit the token exchange code. + + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: Some("test_resource_server".to_string()), + client_secret: Some(secret), + // From the first step. + code_verifier, + }; + + let token_response = idms_prox_read + .check_oauth2_token_exchange(None, &token_req, ct) + .expect("Failed to perform oauth2 token exchange"); + + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(_)) => {} + _ => assert!(false), + } + + // šŸŽ‰ We got a token! In the future we can then check introspection from this point. + assert!(token_response.token_type == "bearer"); } - #[test] - fn test_idm_oauth2_invalid_token_exchange_requests() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, mut uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); + #[idm_test] + async fn test_idm_oauth2_invalid_authorisation_requests( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + // Test invalid oauth2 authorisation states/requests. + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - // āš ļø We set the uat expiry time to 5 seconds from TEST_CURRENT_TIME. This - // allows all our other tests to pass, but it means when we specifically put the - // clock forward a fraction, the fernet tokens are still valid, but the uat - // is not. - // IE - // |---------------------|------------------| - // TEST_CURRENT_TIME UAT_EXPIRE TOKEN_EXPIRE - // - // This lets us check a variety of time based cases. - uat.expiry = Some( - time::OffsetDateTime::unix_epoch() - + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1), - ); + let (anon_uat, anon_ident) = setup_idm_admin(idms, ct, AuthType::Anonymous).await; + let (idm_admin_uat, idm_admin_ident) = + setup_idm_admin(idms, ct, AuthType::PasswordMfa).await; - let mut idms_prox_read = task::block_on(idms.proxy_read()); + // Need a uat from a user not in the group. Probs anonymous. + let idms_prox_read = idms.proxy_read().await; - // == Setup the authorisation request - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + let pkce_request = Some(PkceRequest { + code_challenge: Base64UrlSafeData(code_challenge), + code_challenge_method: CodeChallengeMethod::S256, + }); - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // * response type != code. + let auth_req = AuthorisationRequest { + response_type: "NOTCODE".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: pkce_request.clone(), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } + assert!( + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::UnsupportedResponseType + ); - // == Submit the token exchange code. + // * No pkce in pkce enforced mode. + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: None, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - // Invalid token exchange - // * invalid client_authz (not base64) - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code.clone(), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - // From the first step. - code_verifier: code_verifier.clone(), - }; + assert!( + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::InvalidRequest + ); - assert!( - idms_prox_read - .check_oauth2_token_exchange(Some("not base64"), &token_req, ct) - .unwrap_err() - == Oauth2Error::AuthenticationRequired - ); + // * invalid rs name + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "NOT A REAL RESOURCE SERVER".to_string(), + state: "123".to_string(), + pkce_request: pkce_request.clone(), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - // * doesn't have : - let client_authz = Some(base64::encode(format!("test_resource_server {secret}"))); - assert!( - idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .unwrap_err() - == Oauth2Error::AuthenticationRequired - ); + assert!( + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::InvalidClientId + ); - // * invalid client_id - let client_authz = Some(base64::encode(format!("NOT A REAL SERVER:{secret}"))); - assert!( - idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .unwrap_err() - == Oauth2Error::AuthenticationRequired - ); + // * mis match origin in the redirect. + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: pkce_request.clone(), + redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - // * valid client_id, but invalid secret - let client_authz = Some(base64::encode("test_resource_server:12345")); - assert!( - idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .unwrap_err() - == Oauth2Error::AuthenticationRequired - ); + assert!( + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::InvalidOrigin + ); - // āœ… Now the valid client_authz is in place. - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); - // * expired exchange code (took too long) - assert!( - idms_prox_read - .check_oauth2_token_exchange( - client_authz.as_deref(), - &token_req, - ct + Duration::from_secs(TOKEN_EXPIRE) - ) - .unwrap_err() - == Oauth2Error::InvalidRequest - ); + // Requested scope is not available + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: pkce_request.clone(), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "invalid_scope read".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - // * Uat has expired! - // NOTE: This is setup EARLY in the test, by manipulation of the UAT expiry. - assert!( - idms_prox_read - .check_oauth2_token_exchange( - client_authz.as_deref(), - &token_req, - ct + Duration::from_secs(UAT_EXPIRE) - ) - .unwrap_err() - == Oauth2Error::AccessDenied - ); + assert!( + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::AccessDenied + ); - // * incorrect grant_type - let token_req = AccessTokenRequest { - grant_type: "INCORRECT GRANT TYPE".to_string(), - code: permit_success.code.clone(), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - code_verifier: code_verifier.clone(), - }; - assert!( - idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .unwrap_err() - == Oauth2Error::InvalidRequest - ); + // Not a member of the group. + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: pkce_request.clone(), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "read openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - // * Incorrect redirect uri - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code.clone(), - redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - code_verifier, - }; - assert!( - idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .unwrap_err() - == Oauth2Error::InvalidOrigin - ); + assert!( + idms_prox_read + .check_oauth2_authorisation(&idm_admin_ident, &idm_admin_uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::AccessDenied + ); - // * code verifier incorrect - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - code_verifier: Some("12345".to_string()), - }; - assert!( - idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .unwrap_err() - == Oauth2Error::InvalidRequest - ); - } - ) + // Deny Anonymous auth methods + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "read openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; + + assert!( + idms_prox_read + .check_oauth2_authorisation(&anon_ident, &anon_uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::AccessDenied + ); } - #[test] - fn test_idm_oauth2_token_introspect() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + #[idm_test] + async fn test_idm_oauth2_invalid_authorisation_permit_requests( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + // Test invalid oauth2 authorisation states/requests. + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let (uat2, ident2) = { + let mut idms_prox_write = idms.proxy_write(ct).await; + let account = idms_prox_write + .target_to_account(UUID_IDM_ADMIN) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat2 = account + .to_userauthtoken( + session_id, + ct, + AuthType::PasswordMfa, + Some(AUTH_SESSION_EXPIRY), + ) + .expect("Unable to create uat"); + let ident2 = idms_prox_write + .process_uat_to_identity(&uat2, ct) + .expect("Unable to process uat"); + (uat2, ident2) + }; - // == Setup the authorisation request - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, + let idms_prox_read = idms.proxy_read().await; + + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); + + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; + + // Invalid permits + // * expired token, aka past ttl. + assert!( + idms_prox_read + .check_oauth2_authorise_permit( &ident, &uat, - ct, - code_challenge, - "openid".to_string() - ); + &consent_token, + ct + Duration::from_secs(TOKEN_EXPIRE), + ) + .unwrap_err() + == OperationError::CryptographyError + ); - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // * incorrect ident + // We get another uat, but for a different user, and we'll introduce these + // inconsistently to cause confusion. - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + assert!( + idms_prox_read + .check_oauth2_authorise_permit(&ident2, &uat, &consent_token, ct,) + .unwrap_err() + == OperationError::InvalidSessionState + ); - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } - - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - code_verifier, - }; - let oauth2_token = idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .expect("Unable to exchange for oauth2 token"); - - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(_)) => {} - _ => assert!(false), - } - - // Okay, now we have the token, we can check it works with introspect. - let intr_request = AccessTokenIntrospectRequest { - token: oauth2_token.access_token, - token_type_hint: None, - }; - let intr_response = idms_prox_read - .check_oauth2_token_introspect( - client_authz.as_deref().unwrap(), - &intr_request, - ct, - ) - .expect("Failed to inspect token"); - - eprintln!("šŸ‘‰ {intr_response:?}"); - assert!(intr_response.active); - assert!(intr_response.scope.as_deref() == Some("openid supplement")); - assert!(intr_response.client_id.as_deref() == Some("test_resource_server")); - assert!(intr_response.username.as_deref() == Some("admin@example.com")); - assert!(intr_response.token_type.as_deref() == Some("access_token")); - assert!(intr_response.iat == Some(ct.as_secs() as i64)); - assert!(intr_response.nbf == Some(ct.as_secs() as i64)); - - drop(idms_prox_read); - // start a write, - - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - // Expire the account, should cause introspect to return inactive. - let v_expire = - Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1)); - let me_inv_m = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![Modify::Present( - AttrString::from("account_expire"), - v_expire, - )]), - ) - }; - // go! - assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - - // start a new read - // check again. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let intr_response = idms_prox_read - .check_oauth2_token_introspect(&client_authz.unwrap(), &intr_request, ct) - .expect("Failed to inspect token"); - - assert!(!intr_response.active); - } - ) + // * incorrect session id + assert!( + idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat2, &consent_token, ct,) + .unwrap_err() + == OperationError::InvalidSessionState + ); } - #[test] - fn test_idm_oauth2_token_revoke() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - // First, setup to get a token. - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + #[idm_test] + async fn test_idm_oauth2_invalid_token_exchange_requests( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, mut uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - let mut idms_prox_read = task::block_on(idms.proxy_read()); + // āš ļø We set the uat expiry time to 5 seconds from TEST_CURRENT_TIME. This + // allows all our other tests to pass, but it means when we specifically put the + // clock forward a fraction, the fernet tokens are still valid, but the uat + // is not. + // IE + // |---------------------|------------------| + // TEST_CURRENT_TIME UAT_EXPIRE TOKEN_EXPIRE + // + // This lets us check a variety of time based cases. + uat.expiry = Some( + time::OffsetDateTime::unix_epoch() + + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1), + ); - // == Setup the authorisation request - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let mut idms_prox_read = idms.proxy_read().await; - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // == Setup the authorisation request + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - code_verifier, - }; - let oauth2_token = idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .expect("Unable to exchange for oauth2 token"); + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - drop(idms_prox_read); + // == Submit the token exchange code. - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(osr)) => { - // Process it to ensure the record exists. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // Invalid token exchange + // * invalid client_authz (not base64) + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code.clone(), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + // From the first step. + code_verifier: code_verifier.clone(), + }; - assert!(idms_prox_write.process_oauth2sessionrecord(&osr).is_ok()); + assert!( + idms_prox_read + .check_oauth2_token_exchange(Some("not base64"), &token_req, ct) + .unwrap_err() + == Oauth2Error::AuthenticationRequired + ); - assert!(idms_prox_write.commit().is_ok()); - } - _ => assert!(false), - } + // * doesn't have : + let client_authz = Some(base64::encode(format!("test_resource_server {secret}"))); + assert!( + idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .unwrap_err() + == Oauth2Error::AuthenticationRequired + ); - // Okay, now we have the token, we can check behaviours with the revoke interface. + // * invalid client_id + let client_authz = Some(base64::encode(format!("NOT A REAL SERVER:{secret}"))); + assert!( + idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .unwrap_err() + == Oauth2Error::AuthenticationRequired + ); - // First, assert it is valid, similar to the introspect api. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let intr_request = AccessTokenIntrospectRequest { - token: oauth2_token.access_token.clone(), - token_type_hint: None, - }; - let intr_response = idms_prox_read - .check_oauth2_token_introspect( - client_authz.as_deref().unwrap(), - &intr_request, - ct, - ) - .expect("Failed to inspect token"); - eprintln!("šŸ‘‰ {intr_response:?}"); - assert!(intr_response.active); - drop(idms_prox_read); + // * valid client_id, but invalid secret + let client_authz = Some(base64::encode("test_resource_server:12345")); + assert!( + idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .unwrap_err() + == Oauth2Error::AuthenticationRequired + ); - // First, the revoke needs basic auth. Provide incorrect auth, and we fail. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // āœ… Now the valid client_authz is in place. + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + // * expired exchange code (took too long) + assert!( + idms_prox_read + .check_oauth2_token_exchange( + client_authz.as_deref(), + &token_req, + ct + Duration::from_secs(TOKEN_EXPIRE) + ) + .unwrap_err() + == Oauth2Error::InvalidRequest + ); - let bad_client_authz = Some(base64::encode("test_resource_server:12345")); - let revoke_request = TokenRevokeRequest { - token: oauth2_token.access_token.clone(), - token_type_hint: None, - }; - let e = idms_prox_write - .oauth2_token_revoke(bad_client_authz.as_deref().unwrap(), &revoke_request, ct) - .unwrap_err(); - assert!(matches!(e, Oauth2Error::AuthenticationRequired)); - assert!(idms_prox_write.commit().is_ok()); + // * Uat has expired! + // NOTE: This is setup EARLY in the test, by manipulation of the UAT expiry. + assert!( + idms_prox_read + .check_oauth2_token_exchange( + client_authz.as_deref(), + &token_req, + ct + Duration::from_secs(UAT_EXPIRE) + ) + .unwrap_err() + == Oauth2Error::AccessDenied + ); - // Now submit a non-existent/invalid token. Does not affect our tokens validity. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let revoke_request = TokenRevokeRequest { - token: "this is an invalid token, nothing will happen!".to_string(), - token_type_hint: None, - }; - let e = idms_prox_write - .oauth2_token_revoke(client_authz.as_deref().unwrap(), &revoke_request, ct) - .unwrap_err(); - assert!(matches!(e, Oauth2Error::InvalidRequest)); - assert!(idms_prox_write.commit().is_ok()); + // * incorrect grant_type + let token_req = AccessTokenRequest { + grant_type: "INCORRECT GRANT TYPE".to_string(), + code: permit_success.code.clone(), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + code_verifier: code_verifier.clone(), + }; + assert!( + idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .unwrap_err() + == Oauth2Error::InvalidRequest + ); - // Check our token is still valid. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let intr_response = idms_prox_read - .check_oauth2_token_introspect( - client_authz.as_deref().unwrap(), - &intr_request, - ct, - ) - .expect("Failed to inspect token"); - assert!(intr_response.active); - drop(idms_prox_read); + // * Incorrect redirect uri + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code.clone(), + redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + code_verifier, + }; + assert!( + idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .unwrap_err() + == Oauth2Error::InvalidOrigin + ); - // Finally revoke it. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let revoke_request = TokenRevokeRequest { - token: oauth2_token.access_token.clone(), - token_type_hint: None, - }; - assert!(idms_prox_write - .oauth2_token_revoke(client_authz.as_deref().unwrap(), &revoke_request, ct,) - .is_ok()); - assert!(idms_prox_write.commit().is_ok()); - - // Check it is still valid - this is because we are still in the GRACE window. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let intr_response = idms_prox_read - .check_oauth2_token_introspect( - client_authz.as_deref().unwrap(), - &intr_request, - ct, - ) - .expect("Failed to inspect token"); - - assert!(intr_response.active); - drop(idms_prox_read); - - // Check after the grace window, it will be invalid. - let ct = ct + GRACE_WINDOW; - - // Assert it is now invalid. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let intr_response = idms_prox_read - .check_oauth2_token_introspect( - client_authz.as_deref().unwrap(), - &intr_request, - ct, - ) - .expect("Failed to inspect token"); - - assert!(!intr_response.active); - drop(idms_prox_read); - - // A second invalidation of the token "does nothing". - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let revoke_request = TokenRevokeRequest { - token: oauth2_token.access_token, - token_type_hint: None, - }; - assert!(idms_prox_write - .oauth2_token_revoke(client_authz.as_deref().unwrap(), &revoke_request, ct,) - .is_ok()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + // * code verifier incorrect + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + code_verifier: Some("12345".to_string()), + }; + assert!( + idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .unwrap_err() + == Oauth2Error::InvalidRequest + ); } - #[test] - fn test_idm_oauth2_session_cleanup_post_rs_delete() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - // First, setup to get a token. - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + #[idm_test] + async fn test_idm_oauth2_token_introspect( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - // == Setup the authorisation request - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + // == Setup the authorisation request + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - code_verifier, - }; - let _oauth2_token = idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .expect("Unable to exchange for oauth2 token"); + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + code_verifier, + }; + let oauth2_token = idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .expect("Unable to exchange for oauth2 token"); - drop(idms_prox_read); + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(_)) => {} + _ => assert!(false), + } + // Okay, now we have the token, we can check it works with introspect. + let intr_request = AccessTokenIntrospectRequest { + token: oauth2_token.access_token, + token_type_hint: None, + }; + let intr_response = idms_prox_read + .check_oauth2_token_introspect(client_authz.as_deref().unwrap(), &intr_request, ct) + .expect("Failed to inspect token"); + + eprintln!("šŸ‘‰ {intr_response:?}"); + assert!(intr_response.active); + assert!(intr_response.scope.as_deref() == Some("openid supplement")); + assert!(intr_response.client_id.as_deref() == Some("test_resource_server")); + assert!(intr_response.username.as_deref() == Some("admin@example.com")); + assert!(intr_response.token_type.as_deref() == Some("access_token")); + assert!(intr_response.iat == Some(ct.as_secs() as i64)); + assert!(intr_response.nbf == Some(ct.as_secs() as i64)); + + drop(idms_prox_read); + // start a write, + + let mut idms_prox_write = idms.proxy_write(ct).await; + // Expire the account, should cause introspect to return inactive. + let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1)); + let me_inv_m = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![Modify::Present( + AttrString::from("account_expire"), + v_expire, + )]), + ) + }; + // go! + assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_prox_write.commit().is_ok()); + + // start a new read + // check again. + let mut idms_prox_read = idms.proxy_read().await; + let intr_response = idms_prox_read + .check_oauth2_token_introspect(&client_authz.unwrap(), &intr_request, ct) + .expect("Failed to inspect token"); + + assert!(!intr_response.active); + } + + #[idm_test] + async fn test_idm_oauth2_token_revoke(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) { + // First, setup to get a token. + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + + let mut idms_prox_read = idms.proxy_read().await; + + // == Setup the authorisation request + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); + + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; + + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); + + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } + + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + code_verifier, + }; + let oauth2_token = idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .expect("Unable to exchange for oauth2 token"); + + drop(idms_prox_read); + + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(osr)) => { // Process it to ensure the record exists. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + let mut idms_prox_write = idms.proxy_write(ct).await; - // Assert that the session creation was submitted - let session_id = match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(osr)) => { - assert!(idms_prox_write.process_oauth2sessionrecord(&osr).is_ok()); - osr.session_id - } - _ => { - unreachable!(); - } - }; - - // Check it is now there - let entry = idms_prox_write - .qs_write - .internal_search_uuid(UUID_ADMIN) - .expect("failed"); - let valid = entry - .get_ava_as_oauth2session_map("oauth2_session") - .map(|map| map.get(&session_id).is_some()) - .unwrap_or(false); - assert!(valid); - - // Delete the resource server. - - let de = unsafe { - DeleteEvent::new_internal_invalid(filter!(f_eq( - "oauth2_rs_name", - PartialValue::new_iname("test_resource_server") - ))) - }; - - assert!(idms_prox_write.qs_write.delete(&de).is_ok()); - - // Assert the session is gone. This is cleaned up as an artifact of the referential - // integrity plugin. - let entry = idms_prox_write - .qs_write - .internal_search_uuid(UUID_ADMIN) - .expect("failed"); - let valid = entry - .get_ava_as_oauth2session_map("oauth2_session") - .map(|map| map.get(&session_id).is_some()) - .unwrap_or(false); - assert!(!valid); + assert!(idms_prox_write.process_oauth2sessionrecord(&osr).is_ok()); assert!(idms_prox_write.commit().is_ok()); } - ) + _ => assert!(false), + } + + // Okay, now we have the token, we can check behaviours with the revoke interface. + + // First, assert it is valid, similar to the introspect api. + let mut idms_prox_read = idms.proxy_read().await; + let intr_request = AccessTokenIntrospectRequest { + token: oauth2_token.access_token.clone(), + token_type_hint: None, + }; + let intr_response = idms_prox_read + .check_oauth2_token_introspect(client_authz.as_deref().unwrap(), &intr_request, ct) + .expect("Failed to inspect token"); + eprintln!("šŸ‘‰ {intr_response:?}"); + assert!(intr_response.active); + drop(idms_prox_read); + + // First, the revoke needs basic auth. Provide incorrect auth, and we fail. + let mut idms_prox_write = idms.proxy_write(ct).await; + + let bad_client_authz = Some(base64::encode("test_resource_server:12345")); + let revoke_request = TokenRevokeRequest { + token: oauth2_token.access_token.clone(), + token_type_hint: None, + }; + let e = idms_prox_write + .oauth2_token_revoke(bad_client_authz.as_deref().unwrap(), &revoke_request, ct) + .unwrap_err(); + assert!(matches!(e, Oauth2Error::AuthenticationRequired)); + assert!(idms_prox_write.commit().is_ok()); + + // Now submit a non-existent/invalid token. Does not affect our tokens validity. + let mut idms_prox_write = idms.proxy_write(ct).await; + let revoke_request = TokenRevokeRequest { + token: "this is an invalid token, nothing will happen!".to_string(), + token_type_hint: None, + }; + let e = idms_prox_write + .oauth2_token_revoke(client_authz.as_deref().unwrap(), &revoke_request, ct) + .unwrap_err(); + assert!(matches!(e, Oauth2Error::InvalidRequest)); + assert!(idms_prox_write.commit().is_ok()); + + // Check our token is still valid. + let mut idms_prox_read = idms.proxy_read().await; + let intr_response = idms_prox_read + .check_oauth2_token_introspect(client_authz.as_deref().unwrap(), &intr_request, ct) + .expect("Failed to inspect token"); + assert!(intr_response.active); + drop(idms_prox_read); + + // Finally revoke it. + let mut idms_prox_write = idms.proxy_write(ct).await; + let revoke_request = TokenRevokeRequest { + token: oauth2_token.access_token.clone(), + token_type_hint: None, + }; + assert!(idms_prox_write + .oauth2_token_revoke(client_authz.as_deref().unwrap(), &revoke_request, ct,) + .is_ok()); + assert!(idms_prox_write.commit().is_ok()); + + // Check it is still valid - this is because we are still in the GRACE window. + let mut idms_prox_read = idms.proxy_read().await; + let intr_response = idms_prox_read + .check_oauth2_token_introspect(client_authz.as_deref().unwrap(), &intr_request, ct) + .expect("Failed to inspect token"); + + assert!(intr_response.active); + drop(idms_prox_read); + + // Check after the grace window, it will be invalid. + let ct = ct + GRACE_WINDOW; + + // Assert it is now invalid. + let mut idms_prox_read = idms.proxy_read().await; + let intr_response = idms_prox_read + .check_oauth2_token_introspect(client_authz.as_deref().unwrap(), &intr_request, ct) + .expect("Failed to inspect token"); + + assert!(!intr_response.active); + drop(idms_prox_read); + + // A second invalidation of the token "does nothing". + let mut idms_prox_write = idms.proxy_write(ct).await; + let revoke_request = TokenRevokeRequest { + token: oauth2_token.access_token, + token_type_hint: None, + }; + assert!(idms_prox_write + .oauth2_token_revoke(client_authz.as_deref().unwrap(), &revoke_request, ct,) + .is_ok()); + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_oauth2_authorisation_reject() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); + #[idm_test] + async fn test_idm_oauth2_session_cleanup_post_rs_delete( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + // First, setup to get a token. + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); - let (uat2, ident2) = { - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let account = idms_prox_write - .target_to_account(UUID_IDM_ADMIN) - .expect("account must exist"); - let session_id = uuid::Uuid::new_v4(); - let uat2 = account - .to_userauthtoken( - session_id, - ct, - AuthType::PasswordMfa, - Some(AUTH_SESSION_EXPIRY), - ) - .expect("Unable to create uat"); - let ident2 = idms_prox_write - .process_uat_to_identity(&uat2, ct) - .expect("Unable to process uat"); - (uat2, ident2) - }; + let mut idms_prox_read = idms.proxy_read().await; - let idms_prox_read = task::block_on(idms.proxy_read()); - let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap(); - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + // == Setup the authorisation request + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Check reject behaviour - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); - - let consent_token = if let AuthoriseResponse::ConsentRequested { - consent_token, .. - } = consent_request - { + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { consent_token } else { unreachable!(); }; - let reject_success = idms_prox_read - .check_oauth2_authorise_reject(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 reject"); + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - assert!(reject_success == redirect_uri); + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - // Too much time past to reject - let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301); - assert!( - idms_prox_read - .check_oauth2_authorise_reject(&ident, &uat, &consent_token, past_ct) - .unwrap_err() - == OperationError::CryptographyError - ); + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + code_verifier, + }; + let _oauth2_token = idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .expect("Unable to exchange for oauth2 token"); - // Invalid consent token - assert!( - idms_prox_read - .check_oauth2_authorise_reject(&ident, &uat, "not a token", ct) - .unwrap_err() - == OperationError::CryptographyError - ); + drop(idms_prox_read); - // Wrong UAT - assert!( - idms_prox_read - .check_oauth2_authorise_reject(&ident, &uat2, &consent_token, ct) - .unwrap_err() - == OperationError::InvalidSessionState - ); - // Wrong ident - assert!( - idms_prox_read - .check_oauth2_authorise_reject(&ident2, &uat, &consent_token, ct) - .unwrap_err() - == OperationError::InvalidSessionState - ); - }) + // Process it to ensure the record exists. + let mut idms_prox_write = idms.proxy_write(ct).await; + + // Assert that the session creation was submitted + let session_id = match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(osr)) => { + assert!(idms_prox_write.process_oauth2sessionrecord(&osr).is_ok()); + osr.session_id + } + _ => { + unreachable!(); + } + }; + + // Check it is now there + let entry = idms_prox_write + .qs_write + .internal_search_uuid(UUID_ADMIN) + .expect("failed"); + let valid = entry + .get_ava_as_oauth2session_map("oauth2_session") + .map(|map| map.get(&session_id).is_some()) + .unwrap_or(false); + assert!(valid); + + // Delete the resource server. + + let de = unsafe { + DeleteEvent::new_internal_invalid(filter!(f_eq( + "oauth2_rs_name", + PartialValue::new_iname("test_resource_server") + ))) + }; + + assert!(idms_prox_write.qs_write.delete(&de).is_ok()); + + // Assert the session is gone. This is cleaned up as an artifact of the referential + // integrity plugin. + let entry = idms_prox_write + .qs_write + .internal_search_uuid(UUID_ADMIN) + .expect("failed"); + let valid = entry + .get_ava_as_oauth2session_map("oauth2_session") + .map(|map| map.get(&session_id).is_some()) + .unwrap_or(false); + assert!(!valid); + + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_oauth2_openid_discovery() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, _uat, _ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); + #[idm_test] + async fn test_idm_oauth2_authorisation_reject( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - let idms_prox_read = task::block_on(idms.proxy_read()); + let (uat2, ident2) = { + let mut idms_prox_write = idms.proxy_write(ct).await; + let account = idms_prox_write + .target_to_account(UUID_IDM_ADMIN) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat2 = account + .to_userauthtoken( + session_id, + ct, + AuthType::PasswordMfa, + Some(AUTH_SESSION_EXPIRY), + ) + .expect("Unable to create uat"); + let ident2 = idms_prox_write + .process_uat_to_identity(&uat2, ct) + .expect("Unable to process uat"); + (uat2, ident2) + }; - // check the discovery end point works as we expect - assert!( - idms_prox_read - .oauth2_openid_discovery("nosuchclient") - .unwrap_err() - == OperationError::NoMatchingEntries - ); + let idms_prox_read = idms.proxy_read().await; + let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap(); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - assert!( - idms_prox_read - .oauth2_openid_publickey("nosuchclient") - .unwrap_err() - == OperationError::NoMatchingEntries - ); + // Check reject behaviour + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - let discovery = idms_prox_read - .oauth2_openid_discovery("test_resource_server") - .expect("Failed to get discovery"); - - let mut jwkset = idms_prox_read - .oauth2_openid_publickey("test_resource_server") - .expect("Failed to get public key"); - - let jwk = jwkset.keys.pop().expect("no such jwk"); - - match jwk { - Jwk::EC { alg, use_, kid, .. } => { - match ( - alg.unwrap(), - &discovery.id_token_signing_alg_values_supported[0], - ) { - (JwaAlg::ES256, IdTokenSignAlg::ES256) => {} - _ => panic!(), - }; - assert!(use_.unwrap() == JwkUse::Sig); - assert!(kid.is_some()) - } - _ => panic!(), + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); }; - assert!( - discovery.issuer - == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server") - .unwrap() - ); + let reject_success = idms_prox_read + .check_oauth2_authorise_reject(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 reject"); - assert!( - discovery.authorization_endpoint - == Url::parse("https://idm.example.com/ui/oauth2").unwrap() - ); + assert!(reject_success == redirect_uri); - assert!( - discovery.token_endpoint - == Url::parse("https://idm.example.com/oauth2/token").unwrap() - ); + // Too much time past to reject + let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301); + assert!( + idms_prox_read + .check_oauth2_authorise_reject(&ident, &uat, &consent_token, past_ct) + .unwrap_err() + == OperationError::CryptographyError + ); - assert!( - discovery.userinfo_endpoint - == Some( - Url::parse( - "https://idm.example.com/oauth2/openid/test_resource_server/userinfo" - ) - .unwrap() - ) - ); + // Invalid consent token + assert!( + idms_prox_read + .check_oauth2_authorise_reject(&ident, &uat, "not a token", ct) + .unwrap_err() + == OperationError::CryptographyError + ); - assert!( - discovery.jwks_uri - == Url::parse( - "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk" + // Wrong UAT + assert!( + idms_prox_read + .check_oauth2_authorise_reject(&ident, &uat2, &consent_token, ct) + .unwrap_err() + == OperationError::InvalidSessionState + ); + // Wrong ident + assert!( + idms_prox_read + .check_oauth2_authorise_reject(&ident2, &uat, &consent_token, ct) + .unwrap_err() + == OperationError::InvalidSessionState + ); + } + + #[idm_test] + async fn test_idm_oauth2_openid_discovery( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, _uat, _ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; + + let idms_prox_read = idms.proxy_read().await; + + // check the discovery end point works as we expect + assert!( + idms_prox_read + .oauth2_openid_discovery("nosuchclient") + .unwrap_err() + == OperationError::NoMatchingEntries + ); + + assert!( + idms_prox_read + .oauth2_openid_publickey("nosuchclient") + .unwrap_err() + == OperationError::NoMatchingEntries + ); + + let discovery = idms_prox_read + .oauth2_openid_discovery("test_resource_server") + .expect("Failed to get discovery"); + + let mut jwkset = idms_prox_read + .oauth2_openid_publickey("test_resource_server") + .expect("Failed to get public key"); + + let jwk = jwkset.keys.pop().expect("no such jwk"); + + match jwk { + Jwk::EC { alg, use_, kid, .. } => { + match ( + alg.unwrap(), + &discovery.id_token_signing_alg_values_supported[0], + ) { + (JwaAlg::ES256, IdTokenSignAlg::ES256) => {} + _ => panic!(), + }; + assert!(use_.unwrap() == JwkUse::Sig); + assert!(kid.is_some()) + } + _ => panic!(), + }; + + assert!( + discovery.issuer + == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server") + .unwrap() + ); + + assert!( + discovery.authorization_endpoint + == Url::parse("https://idm.example.com/ui/oauth2").unwrap() + ); + + assert!( + discovery.token_endpoint == Url::parse("https://idm.example.com/oauth2/token").unwrap() + ); + + assert!( + discovery.userinfo_endpoint + == Some( + Url::parse( + "https://idm.example.com/oauth2/openid/test_resource_server/userinfo" ) .unwrap() - ); + ) + ); - eprintln!("{:?}", discovery.scopes_supported); - assert!( - discovery.scopes_supported - == Some(vec![ - "groups".to_string(), - "openid".to_string(), - "supplement".to_string(), - ]) - ); + assert!( + discovery.jwks_uri + == Url::parse( + "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk" + ) + .unwrap() + ); - assert!(discovery.response_types_supported == vec![ResponseType::Code]); - assert!(discovery.response_modes_supported == vec![ResponseMode::Query]); - assert!(discovery.grant_types_supported == vec![GrantType::AuthorisationCode]); - assert!(discovery.subject_types_supported == vec![SubjectType::Public]); - assert!(discovery.id_token_signing_alg_values_supported == vec![IdTokenSignAlg::ES256]); - assert!(discovery.userinfo_signing_alg_values_supported.is_none()); - assert!( - discovery.token_endpoint_auth_methods_supported - == vec![ - TokenEndpointAuthMethod::ClientSecretBasic, - TokenEndpointAuthMethod::ClientSecretPost - ] - ); - assert!(discovery.display_values_supported == Some(vec![DisplayValue::Page])); - assert!(discovery.claim_types_supported == vec![ClaimType::Normal]); - assert!(discovery.claims_supported.is_none()); - assert!(discovery.service_documentation.is_some()); + eprintln!("{:?}", discovery.scopes_supported); + assert!( + discovery.scopes_supported + == Some(vec![ + "groups".to_string(), + "openid".to_string(), + "supplement".to_string(), + ]) + ); - assert!(discovery.registration_endpoint.is_none()); - assert!(discovery.acr_values_supported.is_none()); - assert!(discovery.id_token_encryption_alg_values_supported.is_none()); - assert!(discovery.id_token_encryption_enc_values_supported.is_none()); - assert!(discovery.userinfo_encryption_alg_values_supported.is_none()); - assert!(discovery.userinfo_encryption_enc_values_supported.is_none()); - assert!(discovery - .request_object_signing_alg_values_supported - .is_none()); - assert!(discovery - .request_object_encryption_alg_values_supported - .is_none()); - assert!(discovery - .request_object_encryption_enc_values_supported - .is_none()); - assert!(discovery - .token_endpoint_auth_signing_alg_values_supported - .is_none()); - assert!(discovery.claims_locales_supported.is_none()); - assert!(discovery.ui_locales_supported.is_none()); - assert!(discovery.op_policy_uri.is_none()); - assert!(discovery.op_tos_uri.is_none()); - assert!(!discovery.claims_parameter_supported); - assert!(!discovery.request_uri_parameter_supported); - assert!(!discovery.require_request_uri_registration); - assert!(discovery.request_parameter_supported); - }) + assert!(discovery.response_types_supported == vec![ResponseType::Code]); + assert!(discovery.response_modes_supported == vec![ResponseMode::Query]); + assert!(discovery.grant_types_supported == vec![GrantType::AuthorisationCode]); + assert!(discovery.subject_types_supported == vec![SubjectType::Public]); + assert!(discovery.id_token_signing_alg_values_supported == vec![IdTokenSignAlg::ES256]); + assert!(discovery.userinfo_signing_alg_values_supported.is_none()); + assert!( + discovery.token_endpoint_auth_methods_supported + == vec![ + TokenEndpointAuthMethod::ClientSecretBasic, + TokenEndpointAuthMethod::ClientSecretPost + ] + ); + assert!(discovery.display_values_supported == Some(vec![DisplayValue::Page])); + assert!(discovery.claim_types_supported == vec![ClaimType::Normal]); + assert!(discovery.claims_supported.is_none()); + assert!(discovery.service_documentation.is_some()); + + assert!(discovery.registration_endpoint.is_none()); + assert!(discovery.acr_values_supported.is_none()); + assert!(discovery.id_token_encryption_alg_values_supported.is_none()); + assert!(discovery.id_token_encryption_enc_values_supported.is_none()); + assert!(discovery.userinfo_encryption_alg_values_supported.is_none()); + assert!(discovery.userinfo_encryption_enc_values_supported.is_none()); + assert!(discovery + .request_object_signing_alg_values_supported + .is_none()); + assert!(discovery + .request_object_encryption_alg_values_supported + .is_none()); + assert!(discovery + .request_object_encryption_enc_values_supported + .is_none()); + assert!(discovery + .token_endpoint_auth_signing_alg_values_supported + .is_none()); + assert!(discovery.claims_locales_supported.is_none()); + assert!(discovery.ui_locales_supported.is_none()); + assert!(discovery.op_policy_uri.is_none()); + assert!(discovery.op_tos_uri.is_none()); + assert!(!discovery.claims_parameter_supported); + assert!(!discovery.request_uri_parameter_supported); + assert!(!discovery.require_request_uri_registration); + assert!(discovery.request_parameter_supported); } - #[test] - fn test_idm_oauth2_openid_extensions() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + #[idm_test] + async fn test_idm_oauth2_openid_extensions( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - // == Submit the token exchange code. - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - // From the first step. - code_verifier, - }; + // == Submit the token exchange code. + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + // From the first step. + code_verifier, + }; - let token_response = idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .expect("Failed to perform oauth2 token exchange"); + let token_response = idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .expect("Failed to perform oauth2 token exchange"); - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(_)) => {} - _ => assert!(false), - } + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(_)) => {} + _ => assert!(false), + } - // šŸŽ‰ We got a token! - assert!(token_response.token_type == "bearer"); + // šŸŽ‰ We got a token! + assert!(token_response.token_type == "bearer"); - let id_token = token_response.id_token.expect("No id_token in response!"); - let access_token = token_response.access_token; + let id_token = token_response.id_token.expect("No id_token in response!"); + let access_token = token_response.access_token; - let mut jwkset = idms_prox_read - .oauth2_openid_publickey("test_resource_server") - .expect("Failed to get public key"); - let public_jwk = jwkset.keys.pop().expect("no such jwk"); + let mut jwkset = idms_prox_read + .oauth2_openid_publickey("test_resource_server") + .expect("Failed to get public key"); + let public_jwk = jwkset.keys.pop().expect("no such jwk"); - let jws_validator = - JwsValidator::try_from(&public_jwk).expect("failed to build validator"); + let jws_validator = JwsValidator::try_from(&public_jwk).expect("failed to build validator"); - let oidc_unverified = - OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); + let oidc_unverified = + OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); - let iat = ct.as_secs() as i64; + let iat = ct.as_secs() as i64; - let oidc = oidc_unverified - .validate(&jws_validator, iat) - .expect("Failed to verify oidc"); + let oidc = oidc_unverified + .validate(&jws_validator, iat) + .expect("Failed to verify oidc"); - // Are the id_token values what we expect? - assert!( - oidc.iss - == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server") - .unwrap() - ); - assert!(oidc.sub == OidcSubject::U(UUID_ADMIN)); - assert!(oidc.aud == "test_resource_server"); - assert!(oidc.iat == iat); - assert!(oidc.nbf == Some(iat)); - assert!(oidc.exp == iat + (AUTH_SESSION_EXPIRY as i64)); - assert!(oidc.auth_time.is_none()); - // Is nonce correctly passed through? - assert!(oidc.nonce == Some("abcdef".to_string())); - assert!(oidc.at_hash.is_none()); - assert!(oidc.acr.is_none()); - assert!(oidc.amr == Some(vec!["passwordmfa".to_string()])); - assert!(oidc.azp == Some("test_resource_server".to_string())); - assert!(oidc.jti.is_none()); - assert!(oidc.s_claims.name == Some("System Administrator".to_string())); - assert!(oidc.s_claims.preferred_username == Some("admin@example.com".to_string())); - assert!( - oidc.s_claims.scopes == vec!["openid".to_string(), "supplement".to_string()] - ); - assert!(oidc.claims.is_empty()); - // Does our access token work with the userinfo endpoint? - // Do the id_token details line up to the userinfo? - let userinfo = idms_prox_read - .oauth2_openid_userinfo("test_resource_server", &access_token, ct) - .expect("failed to get userinfo"); - - assert!(oidc.iss == userinfo.iss); - assert!(oidc.sub == userinfo.sub); - assert!(oidc.aud == userinfo.aud); - assert!(oidc.iat == userinfo.iat); - assert!(oidc.nbf == userinfo.nbf); - assert!(oidc.exp == userinfo.exp); - assert!(userinfo.auth_time.is_none()); - assert!(userinfo.nonce.is_none()); - assert!(userinfo.at_hash.is_none()); - assert!(userinfo.acr.is_none()); - assert!(oidc.amr == userinfo.amr); - assert!(oidc.azp == userinfo.azp); - assert!(userinfo.jti.is_none()); - assert!(oidc.s_claims == userinfo.s_claims); - assert!(userinfo.claims.is_empty()); - } - ) - } - - #[test] - fn test_idm_oauth2_openid_short_username() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - // we run the same test as test_idm_oauth2_openid_extensions() - // but change the preferred_username setting on the RS - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, true); - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); - - let mut idms_prox_read = task::block_on(idms.proxy_read()); - - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); - - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; - - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); - - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } - - // == Submit the token exchange code. - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - // From the first step. - code_verifier, - }; - - let token_response = idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .expect("Failed to perform oauth2 token exchange"); - - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(_)) => {} - _ => assert!(false), - } - - let id_token = token_response.id_token.expect("No id_token in response!"); - let access_token = token_response.access_token; - - let mut jwkset = idms_prox_read - .oauth2_openid_publickey("test_resource_server") - .expect("Failed to get public key"); - let public_jwk = jwkset.keys.pop().expect("no such jwk"); - - let jws_validator = - JwsValidator::try_from(&public_jwk).expect("failed to build validator"); - - let oidc_unverified = - OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); - - let iat = ct.as_secs() as i64; - - let oidc = oidc_unverified - .validate(&jws_validator, iat) - .expect("Failed to verify oidc"); - - // Do we have the short username in the token claims? - assert!(oidc.s_claims.preferred_username == Some("admin".to_string())); - // Do the id_token details line up to the userinfo? - let userinfo = idms_prox_read - .oauth2_openid_userinfo("test_resource_server", &access_token, ct) - .expect("failed to get userinfo"); - - assert!(oidc.s_claims == userinfo.s_claims); - } - ) - } - - #[test] - fn test_idm_oauth2_openid_group_claims() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - // we run the same test as test_idm_oauth2_openid_extensions() - // but change the preferred_username setting on the RS - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, true); - let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); - - let mut idms_prox_read = task::block_on(idms.proxy_read()); - - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid groups".to_string() - ); - - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; - - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); - - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } - - // == Submit the token exchange code. - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: None, - client_secret: None, - // From the first step. - code_verifier, - }; - - let token_response = idms_prox_read - .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) - .expect("Failed to perform oauth2 token exchange"); - - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(_)) => {} - _ => assert!(false), - } - - let id_token = token_response.id_token.expect("No id_token in response!"); - let access_token = token_response.access_token; - - let mut jwkset = idms_prox_read - .oauth2_openid_publickey("test_resource_server") - .expect("Failed to get public key"); - let public_jwk = jwkset.keys.pop().expect("no such jwk"); - - let jws_validator = - JwsValidator::try_from(&public_jwk).expect("failed to build validator"); - - let oidc_unverified = - OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); - - let iat = ct.as_secs() as i64; - - let oidc = oidc_unverified - .validate(&jws_validator, iat) - .expect("Failed to verify oidc"); - - // does our id_token contain the expected groups? - assert!(oidc.claims.contains_key(&"groups".to_string())); - - assert!(oidc - .claims - .get(&"groups".to_string()) - .expect("unable to find key") - .as_array() + // Are the id_token values what we expect? + assert!( + oidc.iss + == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server") .unwrap() - .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS))); + ); + assert!(oidc.sub == OidcSubject::U(UUID_ADMIN)); + assert!(oidc.aud == "test_resource_server"); + assert!(oidc.iat == iat); + assert!(oidc.nbf == Some(iat)); + assert!(oidc.exp == iat + (AUTH_SESSION_EXPIRY as i64)); + assert!(oidc.auth_time.is_none()); + // Is nonce correctly passed through? + assert!(oidc.nonce == Some("abcdef".to_string())); + assert!(oidc.at_hash.is_none()); + assert!(oidc.acr.is_none()); + assert!(oidc.amr == Some(vec!["passwordmfa".to_string()])); + assert!(oidc.azp == Some("test_resource_server".to_string())); + assert!(oidc.jti.is_none()); + assert!(oidc.s_claims.name == Some("System Administrator".to_string())); + assert!(oidc.s_claims.preferred_username == Some("admin@example.com".to_string())); + assert!(oidc.s_claims.scopes == vec!["openid".to_string(), "supplement".to_string()]); + assert!(oidc.claims.is_empty()); + // Does our access token work with the userinfo endpoint? + // Do the id_token details line up to the userinfo? + let userinfo = idms_prox_read + .oauth2_openid_userinfo("test_resource_server", &access_token, ct) + .expect("failed to get userinfo"); - // Do the id_token details line up to the userinfo? - let userinfo = idms_prox_read - .oauth2_openid_userinfo("test_resource_server", &access_token, ct) - .expect("failed to get userinfo"); + assert!(oidc.iss == userinfo.iss); + assert!(oidc.sub == userinfo.sub); + assert!(oidc.aud == userinfo.aud); + assert!(oidc.iat == userinfo.iat); + assert!(oidc.nbf == userinfo.nbf); + assert!(oidc.exp == userinfo.exp); + assert!(userinfo.auth_time.is_none()); + assert!(userinfo.nonce.is_none()); + assert!(userinfo.at_hash.is_none()); + assert!(userinfo.acr.is_none()); + assert!(oidc.amr == userinfo.amr); + assert!(oidc.azp == userinfo.azp); + assert!(userinfo.jti.is_none()); + assert!(oidc.s_claims == userinfo.s_claims); + assert!(userinfo.claims.is_empty()); + } - // does the userinfo endpoint provide the same groups? - assert!( - oidc.claims.get(&"groups".to_string()) - == userinfo.claims.get(&"groups".to_string()) - ); - } - ) + #[idm_test] + async fn test_idm_oauth2_openid_short_username( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + // we run the same test as test_idm_oauth2_openid_extensions() + // but change the preferred_username setting on the RS + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, true).await; + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + + let mut idms_prox_read = idms.proxy_read().await; + + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); + + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; + + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); + + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } + + // == Submit the token exchange code. + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + // From the first step. + code_verifier, + }; + + let token_response = idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .expect("Failed to perform oauth2 token exchange"); + + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(_)) => {} + _ => assert!(false), + } + + let id_token = token_response.id_token.expect("No id_token in response!"); + let access_token = token_response.access_token; + + let mut jwkset = idms_prox_read + .oauth2_openid_publickey("test_resource_server") + .expect("Failed to get public key"); + let public_jwk = jwkset.keys.pop().expect("no such jwk"); + + let jws_validator = JwsValidator::try_from(&public_jwk).expect("failed to build validator"); + + let oidc_unverified = + OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); + + let iat = ct.as_secs() as i64; + + let oidc = oidc_unverified + .validate(&jws_validator, iat) + .expect("Failed to verify oidc"); + + // Do we have the short username in the token claims? + assert!(oidc.s_claims.preferred_username == Some("admin".to_string())); + // Do the id_token details line up to the userinfo? + let userinfo = idms_prox_read + .oauth2_openid_userinfo("test_resource_server", &access_token, ct) + .expect("failed to get userinfo"); + + assert!(oidc.s_claims == userinfo.s_claims); + } + + #[idm_test] + async fn test_idm_oauth2_openid_group_claims( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + // we run the same test as test_idm_oauth2_openid_extensions() + // but change the preferred_username setting on the RS + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, true).await; + let client_authz = Some(base64::encode(format!("test_resource_server:{secret}"))); + + let mut idms_prox_read = idms.proxy_read().await; + + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid groups".to_string() + ); + + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; + + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); + + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } + + // == Submit the token exchange code. + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: None, + client_secret: None, + // From the first step. + code_verifier, + }; + + let token_response = idms_prox_read + .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct) + .expect("Failed to perform oauth2 token exchange"); + + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(_)) => {} + _ => assert!(false), + } + + let id_token = token_response.id_token.expect("No id_token in response!"); + let access_token = token_response.access_token; + + let mut jwkset = idms_prox_read + .oauth2_openid_publickey("test_resource_server") + .expect("Failed to get public key"); + let public_jwk = jwkset.keys.pop().expect("no such jwk"); + + let jws_validator = JwsValidator::try_from(&public_jwk).expect("failed to build validator"); + + let oidc_unverified = + OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); + + let iat = ct.as_secs() as i64; + + let oidc = oidc_unverified + .validate(&jws_validator, iat) + .expect("Failed to verify oidc"); + + // does our id_token contain the expected groups? + assert!(oidc.claims.contains_key(&"groups".to_string())); + + assert!(oidc + .claims + .get(&"groups".to_string()) + .expect("unable to find key") + .as_array() + .unwrap() + .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS))); + + // Do the id_token details line up to the userinfo? + let userinfo = idms_prox_read + .oauth2_openid_userinfo("test_resource_server", &access_token, ct) + .expect("failed to get userinfo"); + + // does the userinfo endpoint provide the same groups? + assert!( + oidc.claims.get(&"groups".to_string()) == userinfo.claims.get(&"groups".to_string()) + ); } // Check insecure pkce behaviour. - #[test] - fn test_idm_oauth2_insecure_pkce() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, false, false, false); + #[idm_test] + async fn test_idm_oauth2_insecure_pkce(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, false, false, false).await; - let idms_prox_read = task::block_on(idms.proxy_read()); + let idms_prox_read = idms.proxy_read().await; - // == Setup the authorisation request - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + // == Setup the authorisation request + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - // Even in disable pkce mode, we will allow pkce - let _consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + // Even in disable pkce mode, we will allow pkce + let _consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Check we allow none. - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: None, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: Some("abcdef".to_string()), - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; + // Check we allow none. + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: None, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: Some("abcdef".to_string()), + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .expect("Oauth2 authorisation failed"); - }) + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .expect("Oauth2 authorisation failed"); } - #[test] - fn test_idm_oauth2_openid_legacy_crypto() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, false, true, false); - let mut idms_prox_read = task::block_on(idms.proxy_read()); - // The public key url should offer an rs key - // discovery should offer RS256 - let discovery = idms_prox_read - .oauth2_openid_discovery("test_resource_server") - .expect("Failed to get discovery"); + #[idm_test] + async fn test_idm_oauth2_openid_legacy_crypto( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, false, true, false).await; + let mut idms_prox_read = idms.proxy_read().await; + // The public key url should offer an rs key + // discovery should offer RS256 + let discovery = idms_prox_read + .oauth2_openid_discovery("test_resource_server") + .expect("Failed to get discovery"); - let mut jwkset = idms_prox_read - .oauth2_openid_publickey("test_resource_server") - .expect("Failed to get public key"); + let mut jwkset = idms_prox_read + .oauth2_openid_publickey("test_resource_server") + .expect("Failed to get public key"); - let jwk = jwkset.keys.pop().expect("no such jwk"); - let public_jwk = jwk.clone(); + let jwk = jwkset.keys.pop().expect("no such jwk"); + let public_jwk = jwk.clone(); - match jwk { - Jwk::RSA { alg, use_, kid, .. } => { - match ( - alg.unwrap(), - &discovery.id_token_signing_alg_values_supported[0], - ) { - (JwaAlg::RS256, IdTokenSignAlg::RS256) => {} - _ => panic!(), - }; - assert!(use_.unwrap() == JwkUse::Sig); - assert!(kid.is_some()); - } + match jwk { + Jwk::RSA { alg, use_, kid, .. } => { + match ( + alg.unwrap(), + &discovery.id_token_signing_alg_values_supported[0], + ) { + (JwaAlg::RS256, IdTokenSignAlg::RS256) => {} _ => panic!(), }; - - // Check that the id_token is signed with the correct key. - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); - - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; - - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); - - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } - - // == Submit the token exchange code. - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: Some("test_resource_server".to_string()), - client_secret: Some(secret), - // From the first step. - code_verifier, - }; - - let token_response = idms_prox_read - .check_oauth2_token_exchange(None, &token_req, ct) - .expect("Failed to perform oauth2 token exchange"); - - // Assert that the session creation was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2SessionRecord(_)) => {} - _ => assert!(false), - } - - // šŸŽ‰ We got a token! - assert!(token_response.token_type == "bearer"); - let id_token = token_response.id_token.expect("No id_token in response!"); - - let jws_validator = - JwsValidator::try_from(&public_jwk).expect("failed to build validator"); - - let oidc_unverified = - OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); - - let iat = ct.as_secs() as i64; - - let oidc = oidc_unverified - .validate(&jws_validator, iat) - .expect("Failed to verify oidc"); - - assert!(oidc.sub == OidcSubject::U(UUID_ADMIN)); + assert!(use_.unwrap() == JwkUse::Sig); + assert!(kid.is_some()); } - ) + _ => panic!(), + }; + + // Check that the id_token is signed with the correct key. + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); + + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; + + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); + + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } + + // == Submit the token exchange code. + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: Some("test_resource_server".to_string()), + client_secret: Some(secret), + // From the first step. + code_verifier, + }; + + let token_response = idms_prox_read + .check_oauth2_token_exchange(None, &token_req, ct) + .expect("Failed to perform oauth2 token exchange"); + + // Assert that the session creation was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2SessionRecord(_)) => {} + _ => assert!(false), + } + + // šŸŽ‰ We got a token! + assert!(token_response.token_type == "bearer"); + let id_token = token_response.id_token.expect("No id_token in response!"); + + let jws_validator = JwsValidator::try_from(&public_jwk).expect("failed to build validator"); + + let oidc_unverified = + OidcUnverified::from_str(&id_token).expect("Failed to parse id_token"); + + let iat = ct.as_secs() as i64; + + let oidc = oidc_unverified + .validate(&jws_validator, iat) + .expect("Failed to verify oidc"); + + assert!(oidc.sub == OidcSubject::U(UUID_ADMIN)); } - #[test] - fn test_idm_oauth2_consent_granted_and_changed_workflow() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, true, false, false); + #[idm_test] + async fn test_idm_oauth2_consent_granted_and_changed_workflow( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - let idms_prox_read = task::block_on(idms.proxy_read()); + let idms_prox_read = idms.proxy_read().await; - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Should be in the consent phase; - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // Should be in the consent phase; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // == Manually submit the consent token to the permit for the permit_success - let _permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // == Manually submit the consent token to the permit for the permit_success + let _permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - drop(idms_prox_read); + drop(idms_prox_read); - // Assert that the consent was submitted - let o2cg = match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(o2cg)) => o2cg, - _ => unreachable!(), - }; + // Assert that the consent was submitted + let o2cg = match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(o2cg)) => o2cg, + _ => unreachable!(), + }; - // Manually submit the consent. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - assert!(idms_prox_write.process_oauth2consentgrant(&o2cg).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + // Manually submit the consent. + let mut idms_prox_write = idms.proxy_write(ct).await; + assert!(idms_prox_write.process_oauth2consentgrant(&o2cg).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // == Now try the authorise again, should be in the permitted state. - let mut idms_prox_read = task::block_on(idms.proxy_read()); + // == Now try the authorise again, should be in the permitted state. + let mut idms_prox_read = idms.proxy_read().await; - // We need to reload our identity - let ident = idms_prox_read - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); + // We need to reload our identity + let ident = idms_prox_read + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Should be in the consent phase; - let _permit_success = - if let AuthoriseResponse::Permitted(permit_success) = consent_request { - permit_success - } else { - unreachable!(); - }; + // Should be in the consent phase; + let _permit_success = if let AuthoriseResponse::Permitted(permit_success) = consent_request + { + permit_success + } else { + unreachable!(); + }; - drop(idms_prox_read); + drop(idms_prox_read); - // Great! Now change the scopes on the oauth2 instance, this revokes the permit. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // Great! Now change the scopes on the oauth2 instance, this revokes the permit. + let mut idms_prox_write = idms.proxy_write(ct).await; - let me_extend_scopes = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq( - "oauth2_rs_name", - PartialValue::new_iname("test_resource_server") - )), - ModifyList::new_list(vec![Modify::Present( - AttrString::from("oauth2_rs_scope_map"), - Value::new_oauthscopemap( - UUID_IDM_ALL_ACCOUNTS, - btreeset!["email".to_string(), "openid".to_string()], - ) - .expect("invalid oauthscope"), - )]), + let me_extend_scopes = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq( + "oauth2_rs_name", + PartialValue::new_iname("test_resource_server") + )), + ModifyList::new_list(vec![Modify::Present( + AttrString::from("oauth2_rs_scope_map"), + Value::new_oauthscopemap( + UUID_IDM_ALL_ACCOUNTS, + btreeset!["email".to_string(), "openid".to_string()], ) - }; + .expect("invalid oauthscope"), + )]), + ) + }; - assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // And do the workflow once more to see if we need to consent again. + // And do the workflow once more to see if we need to consent again. - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - // We need to reload our identity - let ident = idms_prox_read - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); + // We need to reload our identity + let ident = idms_prox_read + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: Some(PkceRequest { - code_challenge: Base64UrlSafeData(code_challenge), - code_challenge_method: CodeChallengeMethod::S256, - }), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "openid email".to_string(), - nonce: Some("abcdef".to_string()), - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: Some(PkceRequest { + code_challenge: Base64UrlSafeData(code_challenge), + code_challenge_method: CodeChallengeMethod::S256, + }), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "openid email".to_string(), + nonce: Some("abcdef".to_string()), + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - let consent_request = idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .expect("Oauth2 authorisation failed"); + let consent_request = idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .expect("Oauth2 authorisation failed"); - // Should be in the consent phase; - let _consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // Should be in the consent phase; + let _consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - drop(idms_prox_read); + drop(idms_prox_read); - // Success! We had to consent again due to the change :) + // Success! We had to consent again due to the change :) - // Now change the supplemental scopes on the oauth2 instance, this revokes the permit. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // Now change the supplemental scopes on the oauth2 instance, this revokes the permit. + let mut idms_prox_write = idms.proxy_write(ct).await; - let me_extend_scopes = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq( - "oauth2_rs_name", - PartialValue::new_iname("test_resource_server") - )), - ModifyList::new_list(vec![Modify::Present( - AttrString::from("oauth2_rs_sup_scope_map"), - Value::new_oauthscopemap( - UUID_IDM_ALL_ACCOUNTS, - btreeset!["newscope".to_string()], - ) - .expect("invalid oauthscope"), - )]), + let me_extend_scopes = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq( + "oauth2_rs_name", + PartialValue::new_iname("test_resource_server") + )), + ModifyList::new_list(vec![Modify::Present( + AttrString::from("oauth2_rs_sup_scope_map"), + Value::new_oauthscopemap( + UUID_IDM_ALL_ACCOUNTS, + btreeset!["newscope".to_string()], ) - }; + .expect("invalid oauthscope"), + )]), + ) + }; - assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // And do the workflow once more to see if we need to consent again. + // And do the workflow once more to see if we need to consent again. - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - // We need to reload our identity - let ident = idms_prox_read - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); + // We need to reload our identity + let ident = idms_prox_read + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: Some(PkceRequest { - code_challenge: Base64UrlSafeData(code_challenge), - code_challenge_method: CodeChallengeMethod::S256, - }), - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - // Note the scope isn't requested here! - scope: "openid email".to_string(), - nonce: Some("abcdef".to_string()), - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: Some(PkceRequest { + code_challenge: Base64UrlSafeData(code_challenge), + code_challenge_method: CodeChallengeMethod::S256, + }), + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + // Note the scope isn't requested here! + scope: "openid email".to_string(), + nonce: Some("abcdef".to_string()), + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - let consent_request = idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .expect("Oauth2 authorisation failed"); + let consent_request = idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .expect("Oauth2 authorisation failed"); - // Should be present in the consent phase however! - let _consent_token = if let AuthoriseResponse::ConsentRequested { - consent_token, - scopes, - .. - } = consent_request - { - assert!(scopes.contains(&"newscope".to_string())); - consent_token - } else { - unreachable!(); - }; - } - ) + // Should be present in the consent phase however! + let _consent_token = if let AuthoriseResponse::ConsentRequested { + consent_token, + scopes, + .. + } = consent_request + { + assert!(scopes.contains(&"newscope".to_string())); + consent_token + } else { + unreachable!(); + }; } - #[test] - fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let (_secret, uat, ident, o2rs_uuid) = - setup_oauth2_resource_server(idms, ct, true, false, false); + #[idm_test] + async fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let (_secret, uat, ident, o2rs_uuid) = + setup_oauth2_resource_server(idms, ct, true, false, false).await; - // Assert there are no consent maps yet. - assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none()); + // Assert there are no consent maps yet. + assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none()); - let idms_prox_read = task::block_on(idms.proxy_read()); + let idms_prox_read = idms.proxy_read().await; - let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Should be in the consent phase; - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // Should be in the consent phase; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // == Manually submit the consent token to the permit for the permit_success - let _permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // == Manually submit the consent token to the permit for the permit_success + let _permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - drop(idms_prox_read); + drop(idms_prox_read); - // Assert that the consent was submitted - let o2cg = match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(o2cg)) => o2cg, - _ => unreachable!(), - }; + // Assert that the consent was submitted + let o2cg = match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(o2cg)) => o2cg, + _ => unreachable!(), + }; - // Manually submit the consent. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - assert!(idms_prox_write.process_oauth2consentgrant(&o2cg).is_ok()); + // Manually submit the consent. + let mut idms_prox_write = idms.proxy_write(ct).await; + assert!(idms_prox_write.process_oauth2consentgrant(&o2cg).is_ok()); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); - // Assert that the ident now has the consents. - assert!( - ident.get_oauth2_consent_scopes(o2rs_uuid) - == Some(&btreeset!["openid".to_string(), "supplement".to_string()]) - ); + // Assert that the ident now has the consents. + assert!( + ident.get_oauth2_consent_scopes(o2rs_uuid) + == Some(&btreeset!["openid".to_string(), "supplement".to_string()]) + ); - // Now trigger the delete of the RS - let de = unsafe { - DeleteEvent::new_internal_invalid(filter!(f_eq( - "oauth2_rs_name", - PartialValue::new_iname("test_resource_server") - ))) - }; + // Now trigger the delete of the RS + let de = unsafe { + DeleteEvent::new_internal_invalid(filter!(f_eq( + "oauth2_rs_name", + PartialValue::new_iname("test_resource_server") + ))) + }; - assert!(idms_prox_write.qs_write.delete(&de).is_ok()); - // Assert the consent maps are gone. - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none()); + assert!(idms_prox_write.qs_write.delete(&de).is_ok()); + // Assert the consent maps are gone. + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + assert!(idms_prox_write.commit().is_ok()); } - #[test] + #[idm_test] // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.8 // // It was reported we were vulnerable to this attack, but that isn't the case. First @@ -3746,174 +3663,168 @@ mod tests { // exchange that code exchange with out the verifier, but I'm not sure what damage that would // lead to? Regardless, we test for and close off that possible hole in this test. // - fn test_idm_oauth2_1076_pkce_downgrade() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - // Enable pkce is set to FALSE - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, false, false, false); + async fn test_idm_oauth2_1076_pkce_downgrade( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + // Enable pkce is set to FALSE + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, false, false, false).await; - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - // Get an ident/uat for now. + // Get an ident/uat for now. - // == Setup the authorisation request - // We attempt pkce even though the rs is set to not support pkce. - let (code_verifier, _code_challenge) = create_code_verifier!("Whar Garble"); + // == Setup the authorisation request + // We attempt pkce even though the rs is set to not support pkce. + let (code_verifier, _code_challenge) = create_code_verifier!("Whar Garble"); - // First, the user does not request pkce in their exchange. - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: None, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; + // First, the user does not request pkce in their exchange. + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: None, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - let consent_request = idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .expect("Failed to perform oauth2 authorisation request."); + let consent_request = idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .expect("Failed to perform oauth2 authorisation request."); - // Should be in the consent phase; - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // Should be in the consent phase; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - // == Submit the token exchange code. - // This exchange failed because we submitted a verifier when the code exchange - // has NO code challenge present. - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), - client_id: Some("test_resource_server".to_string()), - client_secret: Some(secret), - // Note the code verifier is set to "something else" - code_verifier, - }; + // == Submit the token exchange code. + // This exchange failed because we submitted a verifier when the code exchange + // has NO code challenge present. + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(), + client_id: Some("test_resource_server".to_string()), + client_secret: Some(secret), + // Note the code verifier is set to "something else" + code_verifier, + }; - // Assert the exchange fails. - assert!(matches!( - idms_prox_read.check_oauth2_token_exchange(None, &token_req, ct), - Err(Oauth2Error::InvalidRequest) - )) - } - ) + // Assert the exchange fails. + assert!(matches!( + idms_prox_read.check_oauth2_token_exchange(None, &token_req, ct), + Err(Oauth2Error::InvalidRequest) + )) } - #[test] + #[idm_test] // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.1 // // If the origin configured is https, do not allow downgrading to http on redirect - fn test_idm_oauth2_redir_http_downgrade() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - // Enable pkce is set to FALSE - let (secret, uat, ident, _) = - setup_oauth2_resource_server(idms, ct, false, false, false); + async fn test_idm_oauth2_redir_http_downgrade( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + // Enable pkce is set to FALSE + let (secret, uat, ident, _) = + setup_oauth2_resource_server(idms, ct, false, false, false).await; - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - // Get an ident/uat for now. + // Get an ident/uat for now. - // == Setup the authorisation request - // We attempt pkce even though the rs is set to not support pkce. - let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); + // == Setup the authorisation request + // We attempt pkce even though the rs is set to not support pkce. + let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble"); - // First, NOTE the lack of https on the redir uri. - let auth_req = AuthorisationRequest { - response_type: "code".to_string(), - client_id: "test_resource_server".to_string(), - state: "123".to_string(), - pkce_request: Some(PkceRequest { - code_challenge: Base64UrlSafeData(code_challenge.clone()), - code_challenge_method: CodeChallengeMethod::S256, - }), - redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(), - scope: "openid".to_string(), - nonce: None, - oidc_ext: Default::default(), - unknown_keys: Default::default(), - }; + // First, NOTE the lack of https on the redir uri. + let auth_req = AuthorisationRequest { + response_type: "code".to_string(), + client_id: "test_resource_server".to_string(), + state: "123".to_string(), + pkce_request: Some(PkceRequest { + code_challenge: Base64UrlSafeData(code_challenge.clone()), + code_challenge_method: CodeChallengeMethod::S256, + }), + redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(), + scope: "openid".to_string(), + nonce: None, + oidc_ext: Default::default(), + unknown_keys: Default::default(), + }; - assert!( - idms_prox_read - .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) - .unwrap_err() - == Oauth2Error::InvalidOrigin - ); + assert!( + idms_prox_read + .check_oauth2_authorisation(&ident, &uat, &auth_req, ct) + .unwrap_err() + == Oauth2Error::InvalidOrigin + ); - // This does have https - let consent_request = good_authorisation_request!( - idms_prox_read, - &ident, - &uat, - ct, - code_challenge, - "openid".to_string() - ); + // This does have https + let consent_request = good_authorisation_request!( + idms_prox_read, + &ident, + &uat, + ct, + code_challenge, + "openid".to_string() + ); - // Should be in the consent phase; - let consent_token = - if let AuthoriseResponse::ConsentRequested { consent_token, .. } = - consent_request - { - consent_token - } else { - unreachable!(); - }; + // Should be in the consent phase; + let consent_token = + if let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request { + consent_token + } else { + unreachable!(); + }; - // == Manually submit the consent token to the permit for the permit_success - let permit_success = idms_prox_read - .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) - .expect("Failed to perform oauth2 permit"); + // == Manually submit the consent token to the permit for the permit_success + let permit_success = idms_prox_read + .check_oauth2_authorise_permit(&ident, &uat, &consent_token, ct) + .expect("Failed to perform oauth2 permit"); - // Assert that the consent was submitted - match idms_delayed.async_rx.blocking_recv() { - Some(DelayedAction::Oauth2ConsentGrant(_)) => {} - _ => assert!(false), - } + // Assert that the consent was submitted + match idms_delayed.async_rx.recv().await { + Some(DelayedAction::Oauth2ConsentGrant(_)) => {} + _ => assert!(false), + } - // == Submit the token exchange code. - // NOTE the url is http again - let token_req = AccessTokenRequest { - grant_type: "authorization_code".to_string(), - code: permit_success.code, - redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(), - client_id: Some("test_resource_server".to_string()), - client_secret: Some(secret), - // Note the code verifier is set to "something else" - code_verifier, - }; + // == Submit the token exchange code. + // NOTE the url is http again + let token_req = AccessTokenRequest { + grant_type: "authorization_code".to_string(), + code: permit_success.code, + redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(), + client_id: Some("test_resource_server".to_string()), + client_secret: Some(secret), + // Note the code verifier is set to "something else" + code_verifier, + }; - // Assert the exchange fails. - assert!(matches!( - idms_prox_read.check_oauth2_token_exchange(None, &token_req, ct), - Err(Oauth2Error::InvalidOrigin) - )) - } - ) + // Assert the exchange fails. + assert!(matches!( + idms_prox_read.check_oauth2_token_exchange(None, &token_req, ct), + Err(Oauth2Error::InvalidOrigin) + )) } } diff --git a/server/lib/src/idm/reauth.rs b/server/lib/src/idm/reauth.rs index 1c5e1ff56..c43813423 100644 --- a/server/lib/src/idm/reauth.rs +++ b/server/lib/src/idm/reauth.rs @@ -75,7 +75,7 @@ mod tests { // Update session is setup. - let cutxn = idms.cred_update_transaction(); + let cutxn = idms.cred_update_transaction().await; let origin = cutxn.get_origin().clone(); let mut wa = WebauthnAuthenticator::new(SoftPasskey::new()); @@ -120,7 +120,7 @@ mod tests { wa: &mut WebauthnAuthenticator, idms_delayed: &mut IdmServerDelayed, ) -> Option { - let mut idms_auth = idms.auth(); + let mut idms_auth = idms.auth().await; let origin = idms_auth.get_origin().clone(); let auth_init = AuthEvent::named_init("testperson"); diff --git a/server/lib/src/idm/scim.rs b/server/lib/src/idm/scim.rs index ccd9ed8a7..3a48adaa1 100644 --- a/server/lib/src/idm/scim.rs +++ b/server/lib/src/idm/scim.rs @@ -1385,8 +1385,6 @@ mod tests { ScimSyncUpdateEvent, }; - use async_std::task; - const TEST_CURRENT_TIME: u64 = 6000; fn create_scim_sync_account( @@ -1416,167 +1414,163 @@ mod tests { (sync_uuid, sync_token) } - #[test] - fn test_idm_scim_sync_basic_function() { - 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_scim_sync_basic_function( + 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)); - let (sync_uuid, sync_token) = create_scim_sync_account(&mut idms_prox_write, ct); + let mut idms_prox_write = idms.proxy_write(ct).await; + let (sync_uuid, sync_token) = create_scim_sync_account(&mut idms_prox_write, ct); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Do a get_state to get the current "state cookie" if any. - let mut idms_prox_read = task::block_on(idms.proxy_read()); + // Do a get_state to get the current "state cookie" if any. + let mut idms_prox_read = idms.proxy_read().await; - let ident = idms_prox_read - .validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct) - .expect("Failed to validate sync token"); + let ident = idms_prox_read + .validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct) + .expect("Failed to validate sync token"); - assert!(Some(sync_uuid) == ident.get_uuid()); + assert!(Some(sync_uuid) == ident.get_uuid()); - let sync_state = idms_prox_read - .scim_sync_get_state(&ident) - .expect("Failed to get current sync state"); - trace!(?sync_state); + let sync_state = idms_prox_read + .scim_sync_get_state(&ident) + .expect("Failed to get current sync state"); + trace!(?sync_state); - assert!(matches!(sync_state, ScimSyncState::Refresh)); + assert!(matches!(sync_state, ScimSyncState::Refresh)); - drop(idms_prox_read); + drop(idms_prox_read); - // Use the current state and update. + // Use the current state and update. - // TODO!!! - }) + // TODO!!! } - #[test] - fn test_idm_scim_sync_token_security() { - 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_scim_sync_token_security( + 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)); + let mut idms_prox_write = idms.proxy_write(ct).await; - let sync_uuid = Uuid::new_v4(); + let sync_uuid = Uuid::new_v4(); - let e1 = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("sync_account")), - ("name", Value::new_iname("test_scim_sync")), - ("uuid", Value::Uuid(sync_uuid)), - ("description", Value::new_utf8s("A test sync agreement")) - ); + let e1 = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("sync_account")), + ("name", Value::new_iname("test_scim_sync")), + ("uuid", Value::Uuid(sync_uuid)), + ("description", Value::new_utf8s("A test sync agreement")) + ); - 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 = GenerateScimSyncTokenEvent::new_internal(sync_uuid, "Sync Connector"); + let gte = GenerateScimSyncTokenEvent::new_internal(sync_uuid, "Sync Connector"); - let sync_token = idms_prox_write - .scim_sync_generate_token(>e, ct) - .expect("failed to generate new scim sync token"); + let sync_token = idms_prox_write + .scim_sync_generate_token(>e, ct) + .expect("failed to generate new scim sync token"); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // -- Check the happy path. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let ident = idms_prox_read - .validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct) - .expect("Failed to validate sync token"); - assert!(Some(sync_uuid) == ident.get_uuid()); - drop(idms_prox_read); + // -- Check the happy path. + let mut idms_prox_read = idms.proxy_read().await; + let ident = idms_prox_read + .validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct) + .expect("Failed to validate sync token"); + assert!(Some(sync_uuid) == ident.get_uuid()); + drop(idms_prox_read); - // -- Revoke the session + // -- Revoke the session - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let me_inv_m = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("test_scim_sync"))), - ModifyList::new_list(vec![Modify::Purged(AttrString::from( - "sync_token_session", - ))]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + let mut idms_prox_write = idms.proxy_write(ct).await; + let me_inv_m = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("test_scim_sync"))), + ModifyList::new_list(vec![Modify::Purged(AttrString::from("sync_token_session"))]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Must fail - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let fail = idms_prox_read - .validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct); - assert!(matches!(fail, Err(OperationError::NotAuthenticated))); - drop(idms_prox_read); + // Must fail + let mut idms_prox_read = idms.proxy_read().await; + let fail = + idms_prox_read.validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct); + assert!(matches!(fail, Err(OperationError::NotAuthenticated))); + drop(idms_prox_read); - // -- New session, reset the JWS - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // -- New session, reset the JWS + let mut idms_prox_write = idms.proxy_write(ct).await; - let gte = GenerateScimSyncTokenEvent::new_internal(sync_uuid, "Sync Connector"); - let sync_token = idms_prox_write - .scim_sync_generate_token(>e, ct) - .expect("failed to generate new scim sync token"); + let gte = GenerateScimSyncTokenEvent::new_internal(sync_uuid, "Sync Connector"); + let sync_token = idms_prox_write + .scim_sync_generate_token(>e, ct) + .expect("failed to generate new scim sync token"); - let me_inv_m = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("test_scim_sync"))), - ModifyList::new_list(vec![Modify::Purged(AttrString::from( - "jws_es256_private_key", - ))]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + let me_inv_m = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("test_scim_sync"))), + ModifyList::new_list(vec![Modify::Purged(AttrString::from( + "jws_es256_private_key", + ))]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let fail = idms_prox_read - .validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct); - assert!(matches!(fail, Err(OperationError::NotAuthenticated))); + let mut idms_prox_read = idms.proxy_read().await; + let fail = + idms_prox_read.validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct); + assert!(matches!(fail, Err(OperationError::NotAuthenticated))); - // -- Forge a session, use wrong types + // -- Forge a session, use wrong types - let sync_entry = idms_prox_read - .qs_read - .internal_search_uuid(sync_uuid) - .expect("Unable to access sync entry"); + let sync_entry = idms_prox_read + .qs_read + .internal_search_uuid(sync_uuid) + .expect("Unable to access sync entry"); - let jws_key = sync_entry - .get_ava_single_jws_key_es256("jws_es256_private_key") - .cloned() - .expect("Missing attribute: jws_es256_private_key"); + let jws_key = sync_entry + .get_ava_single_jws_key_es256("jws_es256_private_key") + .cloned() + .expect("Missing attribute: jws_es256_private_key"); - let sync_tokens = sync_entry - .get_ava_as_apitoken_map("sync_token_session") - .cloned() - .unwrap_or_default(); + let sync_tokens = sync_entry + .get_ava_as_apitoken_map("sync_token_session") + .cloned() + .unwrap_or_default(); - // Steal these from the legit sesh. - let (token_id, issued_at) = sync_tokens - .iter() - .next() - .map(|(k, v)| (*k, v.issued_at)) - .expect("No sync tokens present"); + // Steal these from the legit sesh. + let (token_id, issued_at) = sync_tokens + .iter() + .next() + .map(|(k, v)| (*k, v.issued_at)) + .expect("No sync tokens present"); - let purpose = ApiTokenPurpose::ReadWrite; + let purpose = ApiTokenPurpose::ReadWrite; - let token = Jws::new(ScimSyncToken { - token_id, - issued_at, - purpose, - }); + let token = Jws::new(ScimSyncToken { + token_id, + issued_at, + purpose, + }); - let forged_token = token - .sign(&jws_key) - .map(|jws_signed| jws_signed.to_string()) - .expect("Unable to sign forged token"); + let forged_token = token + .sign(&jws_key) + .map(|jws_signed| jws_signed.to_string()) + .expect("Unable to sign forged token"); - let fail = idms_prox_read - .validate_and_parse_sync_token_to_ident(Some(forged_token.as_str()), ct); - assert!(matches!(fail, Err(OperationError::NotAuthenticated))); - }) + let fail = + idms_prox_read.validate_and_parse_sync_token_to_ident(Some(forged_token.as_str()), ct); + assert!(matches!(fail, Err(OperationError::NotAuthenticated))); } fn test_scim_sync_apply_setup_ident( @@ -1610,142 +1604,139 @@ mod tests { (sync_uuid, ident) } - #[test] - fn test_idm_scim_sync_apply_phase_1_inconsistent() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + #[idm_test] + async fn test_idm_scim_sync_apply_phase_1_inconsistent( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - to_state: ScimSyncState::Refresh, - entries: Vec::default(), - delete_uuids: Vec::default(), - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + to_state: ScimSyncState::Refresh, + entries: Vec::default(), + delete_uuids: Vec::default(), + }; - let res = idms_prox_write.scim_sync_apply_phase_1(&sse, &changes); + let res = idms_prox_write.scim_sync_apply_phase_1(&sse, &changes); - assert!(matches!(res, Err(OperationError::InvalidSyncState))); + assert!(matches!(res, Err(OperationError::InvalidSyncState))); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_scim_sync_apply_phase_2_basic() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + #[idm_test] + async fn test_idm_scim_sync_apply_phase_2_basic( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let user_sync_uuid = uuid::uuid!("91b7aaf2-2445-46ce-8998-96d9f186cc69"); + let user_sync_uuid = uuid::uuid!("91b7aaf2-2445-46ce-8998-96d9f186cc69"); - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - entries: vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_PERSON.to_string()], - id: user_sync_uuid, - external_id: Some("dn=william,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!(( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("william".to_string())) - ),), - }], - delete_uuids: Vec::default(), - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + entries: vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_PERSON.to_string()], + id: user_sync_uuid, + external_id: Some("dn=william,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!(( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("william".to_string())) + ),), + }], + delete_uuids: Vec::default(), + }; - let (sync_uuid, _sync_authority_set, change_entries, _sync_refresh) = idms_prox_write - .scim_sync_apply_phase_1(&sse, &changes) - .expect("Failed to run phase 1"); + let (sync_uuid, _sync_authority_set, change_entries, _sync_refresh) = idms_prox_write + .scim_sync_apply_phase_1(&sse, &changes) + .expect("Failed to run phase 1"); - idms_prox_write - .scim_sync_apply_phase_2(&change_entries, sync_uuid) - .expect("Failed to run phase 2"); + idms_prox_write + .scim_sync_apply_phase_2(&change_entries, sync_uuid) + .expect("Failed to run phase 2"); - let synced_entry = idms_prox_write - .qs_write - .internal_search_uuid(user_sync_uuid) - .expect("Failed to access sync stub entry"); + let synced_entry = idms_prox_write + .qs_write + .internal_search_uuid(user_sync_uuid) + .expect("Failed to access sync stub entry"); - assert!( - synced_entry.get_ava_single_iutf8("sync_external_id") - == Some("dn=william,ou=people,dc=test") - ); - assert!(synced_entry.get_uuid() == user_sync_uuid); + assert!( + synced_entry.get_ava_single_iutf8("sync_external_id") + == Some("dn=william,ou=people,dc=test") + ); + assert!(synced_entry.get_uuid() == user_sync_uuid); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_scim_sync_apply_phase_2_deny_on_tombstone() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + #[idm_test] + async fn test_idm_scim_sync_apply_phase_2_deny_on_tombstone( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let user_sync_uuid = Uuid::new_v4(); - // Create a recycled entry - assert!(idms_prox_write - .qs_write - .internal_create(vec![entry_init!( - ("class", Value::new_class("object")), - ("uuid", Value::Uuid(user_sync_uuid)) - )]) - .is_ok()); + let user_sync_uuid = Uuid::new_v4(); + // Create a recycled entry + assert!(idms_prox_write + .qs_write + .internal_create(vec![entry_init!( + ("class", Value::new_class("object")), + ("uuid", Value::Uuid(user_sync_uuid)) + )]) + .is_ok()); - assert!(idms_prox_write - .qs_write - .internal_delete_uuid(user_sync_uuid) - .is_ok()); + assert!(idms_prox_write + .qs_write + .internal_delete_uuid(user_sync_uuid) + .is_ok()); - // Now create a sync that conflicts with the tombstone uuid. This will be REJECTED. + // Now create a sync that conflicts with the tombstone uuid. This will be REJECTED. - let sse = ScimSyncUpdateEvent { ident }; + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - entries: vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_PERSON.to_string()], - id: user_sync_uuid, - external_id: Some("dn=william,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!(( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("william".to_string())) - ),), - }], - delete_uuids: Vec::default(), - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + entries: vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_PERSON.to_string()], + id: user_sync_uuid, + external_id: Some("dn=william,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!(( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("william".to_string())) + ),), + }], + delete_uuids: Vec::default(), + }; - let (sync_uuid, _sync_authority_set, change_entries, _sync_refresh) = idms_prox_write - .scim_sync_apply_phase_1(&sse, &changes) - .expect("Failed to run phase 1"); + let (sync_uuid, _sync_authority_set, change_entries, _sync_refresh) = idms_prox_write + .scim_sync_apply_phase_1(&sse, &changes) + .expect("Failed to run phase 1"); - let res = idms_prox_write.scim_sync_apply_phase_2(&change_entries, sync_uuid); + let res = idms_prox_write.scim_sync_apply_phase_2(&change_entries, sync_uuid); - assert!(matches!(res, Err(OperationError::InvalidEntryState))); + assert!(matches!(res, Err(OperationError::InvalidEntryState))); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } // Phase 3 @@ -1781,402 +1772,394 @@ mod tests { .and_then(|a| idms_prox_write.commit().map(|()| a)) } - #[test] - fn test_idm_scim_sync_phase_3_basic() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let user_sync_uuid = Uuid::new_v4(); + #[idm_test] + async fn test_idm_scim_sync_phase_3_basic( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let user_sync_uuid = Uuid::new_v4(); - assert!(task::block_on(apply_phase_3_test( - idms, - vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], - id: user_sync_uuid, - external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!(( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) - ),), - }] - )) - .is_ok()); + assert!(apply_phase_3_test( + idms, + vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], + id: user_sync_uuid, + external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!(( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) + ),), + }] + ) + .await + .is_ok()); - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let mut idms_prox_write = idms.proxy_write(ct).await; - let ent = idms_prox_write - .qs_write - .internal_search_uuid(user_sync_uuid) - .expect("Unable to access entry"); + let ent = idms_prox_write + .qs_write + .internal_search_uuid(user_sync_uuid) + .expect("Unable to access entry"); - assert!(ent.get_ava_single_iname("name") == Some("testgroup")); - assert!( - ent.get_ava_single_iutf8("sync_external_id") - == Some("cn=testgroup,ou=people,dc=test") - ); + assert!(ent.get_ava_single_iname("name") == Some("testgroup")); + assert!( + ent.get_ava_single_iutf8("sync_external_id") == Some("cn=testgroup,ou=people,dc=test") + ); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } // -- try to set uuid - #[test] - fn test_idm_scim_sync_phase_3_uuid_manipulation() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let user_sync_uuid = Uuid::new_v4(); + #[idm_test] + async fn test_idm_scim_sync_phase_3_uuid_manipulation( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let user_sync_uuid = Uuid::new_v4(); - assert!(task::block_on(apply_phase_3_test( - idms, - vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], - id: user_sync_uuid, - external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!( - ( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) - ), - ( - "uuid".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String( - "2c019619-f894-4a94-b356-05d371850e3d".to_string() - )) - ) + assert!(apply_phase_3_test( + idms, + vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], + id: user_sync_uuid, + external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!( + ( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) ), - }] - )) - .is_err()); - }) + ( + "uuid".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String( + "2c019619-f894-4a94-b356-05d371850e3d".to_string() + )) + ) + ), + }] + ) + .await + .is_err()); } // -- try to set sync_uuid / sync_object attrs - #[test] - fn test_idm_scim_sync_phase_3_sync_parent_uuid_manipulation() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let user_sync_uuid = Uuid::new_v4(); + #[idm_test] + async fn test_idm_scim_sync_phase_3_sync_parent_uuid_manipulation( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let user_sync_uuid = Uuid::new_v4(); - assert!(task::block_on(apply_phase_3_test( - idms, - vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], - id: user_sync_uuid, - external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!( - ( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) - ), - ( - "sync_parent_uuid".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String( - "2c019619-f894-4a94-b356-05d371850e3d".to_string() - )) - ) + assert!(apply_phase_3_test( + idms, + vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], + id: user_sync_uuid, + external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!( + ( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) ), - }] - )) - .is_err()); - }) + ( + "sync_parent_uuid".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String( + "2c019619-f894-4a94-b356-05d371850e3d".to_string() + )) + ) + ), + }] + ) + .await + .is_err()); } // -- try to add class via class attr (not via scim schema) - #[test] - fn test_idm_scim_sync_phase_3_disallowed_class_forbidden() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let user_sync_uuid = Uuid::new_v4(); + #[idm_test] + async fn test_idm_scim_sync_phase_3_disallowed_class_forbidden( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let user_sync_uuid = Uuid::new_v4(); - assert!(task::block_on(apply_phase_3_test( - idms, - vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], - id: user_sync_uuid, - external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!( - ( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) - ), - ( - "class".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String( - "posixgroup".to_string() - )) - ) + assert!(apply_phase_3_test( + idms, + vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], + id: user_sync_uuid, + external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!( + ( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) ), - }] - )) - .is_err()); - }) + ( + "class".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("posixgroup".to_string())) + ) + ), + }] + ) + .await + .is_err()); } // -- try to add class not in allowed class set (via scim schema) - #[test] - fn test_idm_scim_sync_phase_3_disallowed_class_system() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let user_sync_uuid = Uuid::new_v4(); + #[idm_test] + async fn test_idm_scim_sync_phase_3_disallowed_class_system( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let user_sync_uuid = Uuid::new_v4(); - assert!(task::block_on(apply_phase_3_test( - idms, - vec![ScimEntry { - schemas: vec![format!("{SCIM_SCHEMA_SYNC}system")], - id: user_sync_uuid, - external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!(( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) - ),), - }] - )) - .is_err()); - }) + assert!(apply_phase_3_test( + idms, + vec![ScimEntry { + schemas: vec![format!("{SCIM_SCHEMA_SYNC}system")], + id: user_sync_uuid, + external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!(( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) + ),), + }] + ) + .await + .is_err()); } // Phase 4 // Good delete - requires phase 5 due to need to do two syncs - #[test] - fn test_idm_scim_sync_phase_4_correct_delete() { - run_idm_test!(|_qs: &QueryServer, - idms: &IdmServer, - _idms_delayed: &mut IdmServerDelayed| { - let user_sync_uuid = Uuid::new_v4(); - // Create an entry via sync + #[idm_test] + async fn test_idm_scim_sync_phase_4_correct_delete( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let user_sync_uuid = Uuid::new_v4(); + // Create an entry via sync - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { - ident: ident.clone(), - }; + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let mut idms_prox_write = idms.proxy_write(ct).await; + let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { + ident: ident.clone(), + }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - entries: vec![ScimEntry { - schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], - id: user_sync_uuid, - external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), - meta: None, - attrs: btreemap!(( - "name".to_string(), - ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) - ),), - }], - delete_uuids: Vec::default(), - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + entries: vec![ScimEntry { + schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()], + id: user_sync_uuid, + external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), + meta: None, + attrs: btreemap!(( + "name".to_string(), + ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) + ),), + }], + delete_uuids: Vec::default(), + }; - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Now we can attempt the delete. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let sse = ScimSyncUpdateEvent { ident }; + // Now we can attempt the delete. + let mut idms_prox_write = idms.proxy_write(ct).await; + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![2, 3, 4, 5]), - }, - entries: vec![], - delete_uuids: vec![user_sync_uuid], - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![2, 3, 4, 5]), + }, + entries: vec![], + delete_uuids: vec![user_sync_uuid], + }; - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - // Can't use internal_search_uuid since that applies a mask. - assert!(idms_prox_write - .qs_write - .internal_search(filter_all!(f_eq( - "uuid", - PartialValue::Uuid(user_sync_uuid) - ))) - // Should be none as the entry was masked by being recycled. - .map(|entries| { - assert!(entries.len() == 1); - let ent = entries.get(0).unwrap(); - ent.mask_recycled_ts().is_none() - }) - .unwrap_or(false)); + // Can't use internal_search_uuid since that applies a mask. + assert!(idms_prox_write + .qs_write + .internal_search(filter_all!(f_eq( + "uuid", + PartialValue::Uuid(user_sync_uuid) + ))) + // Should be none as the entry was masked by being recycled. + .map(|entries| { + assert!(entries.len() == 1); + let ent = entries.get(0).unwrap(); + ent.mask_recycled_ts().is_none() + }) + .unwrap_or(false)); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } // Delete that doesn't exist. - #[test] - fn test_idm_scim_sync_phase_4_nonexisting_delete() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + #[idm_test] + async fn test_idm_scim_sync_phase_4_nonexisting_delete( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - // Doesn't exist. If it does, then bless rng. - entries: Vec::default(), - delete_uuids: vec![Uuid::new_v4()], - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + // Doesn't exist. If it does, then bless rng. + entries: Vec::default(), + delete_uuids: vec![Uuid::new_v4()], + }; - // Hard to know what was right here. IMO because it doesn't exist at all, we just ignore it - // because the source sync is being overzealous, or it previously used to exist. Maybe - // it was added and immediately removed. Either way, this is ok because we changed - // nothing. - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - }) + // Hard to know what was right here. IMO because it doesn't exist at all, we just ignore it + // because the source sync is being overzealous, or it previously used to exist. Maybe + // it was added and immediately removed. Either way, this is ok because we changed + // nothing. + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.commit().is_ok()); } // Delete of something outside of agreement control - must fail. - #[test] - fn test_idm_scim_sync_phase_4_out_of_scope_delete() { - 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_scim_sync_phase_4_out_of_scope_delete( + 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 user_sync_uuid = Uuid::new_v4(); - assert!(idms_prox_write - .qs_write - .internal_create(vec![entry_init!( - ("class", Value::new_class("object")), - ("uuid", Value::Uuid(user_sync_uuid)) - )]) - .is_ok()); + let user_sync_uuid = Uuid::new_v4(); + assert!(idms_prox_write + .qs_write + .internal_create(vec![entry_init!( + ("class", Value::new_class("object")), + ("uuid", Value::Uuid(user_sync_uuid)) + )]) + .is_ok()); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - // Doesn't exist. If it does, then bless rng. - entries: Vec::default(), - delete_uuids: vec![user_sync_uuid], - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + // Doesn't exist. If it does, then bless rng. + entries: Vec::default(), + delete_uuids: vec![user_sync_uuid], + }; - // Again, not sure what to do here. I think because this is clearly an overstep of the - // rights of the delete_uuid request, this is an error here. - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_err()); - // assert!(idms_prox_write.commit().is_ok()); - }) + // Again, not sure what to do here. I think because this is clearly an overstep of the + // rights of the delete_uuid request, this is an error here. + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_err()); + // assert!(idms_prox_write.commit().is_ok()); } // Delete already deleted entry. - #[test] - fn test_idm_scim_sync_phase_4_delete_already_deleted() { - 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_scim_sync_phase_4_delete_already_deleted( + 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 user_sync_uuid = Uuid::new_v4(); - assert!(idms_prox_write - .qs_write - .internal_create(vec![entry_init!( - ("class", Value::new_class("object")), - ("uuid", Value::Uuid(user_sync_uuid)) - )]) - .is_ok()); + let user_sync_uuid = Uuid::new_v4(); + assert!(idms_prox_write + .qs_write + .internal_create(vec![entry_init!( + ("class", Value::new_class("object")), + ("uuid", Value::Uuid(user_sync_uuid)) + )]) + .is_ok()); - assert!(idms_prox_write - .qs_write - .internal_delete_uuid(user_sync_uuid) - .is_ok()); + assert!(idms_prox_write + .qs_write + .internal_delete_uuid(user_sync_uuid) + .is_ok()); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - // Doesn't exist. If it does, then bless rng. - entries: Vec::default(), - delete_uuids: vec![user_sync_uuid], - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + // Doesn't exist. If it does, then bless rng. + entries: Vec::default(), + delete_uuids: vec![user_sync_uuid], + }; - // More subtely. There is clearly a theme here. In this case while the sync request - // is trying to delete something out of scope and already deleted, since it already - // is in a recycled state it doesn't matter, it's a no-op. We only care about when - // the delete req applies to a live entry. - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - }) + // More subtely. There is clearly a theme here. In this case while the sync request + // is trying to delete something out of scope and already deleted, since it already + // is in a recycled state it doesn't matter, it's a no-op. We only care about when + // the delete req applies to a live entry. + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.commit().is_ok()); } // Phase 5 - #[test] - fn test_idm_scim_sync_phase_5_from_refresh_to_active() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { - ident: ident.clone(), - }; + #[idm_test] + async fn test_idm_scim_sync_phase_5_from_refresh_to_active( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { + ident: ident.clone(), + }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Refresh, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - entries: Vec::default(), - delete_uuids: Vec::default(), - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Refresh, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + entries: Vec::default(), + delete_uuids: Vec::default(), + }; - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Advance the from -> to state. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let sse = ScimSyncUpdateEvent { ident }; + // Advance the from -> to state. + let mut idms_prox_write = idms.proxy_write(ct).await; + let sse = ScimSyncUpdateEvent { ident }; - let changes = ScimSyncRequest { - from_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), - }, - to_state: ScimSyncState::Active { - cookie: Base64UrlSafeData(vec![2, 3, 4, 5]), - }, - entries: vec![], - delete_uuids: vec![], - }; + let changes = ScimSyncRequest { + from_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![1, 2, 3, 4]), + }, + to_state: ScimSyncState::Active { + cookie: Base64UrlSafeData(vec![2, 3, 4, 5]), + }, + entries: vec![], + delete_uuids: vec![], + }; - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.commit().is_ok()); } // Test the client doing a sync refresh request (active -> refresh). @@ -2202,455 +2185,437 @@ mod tests { .expect("Failed to access entry.") } - #[test] - fn test_idm_scim_sync_refresh_ipa_example_1() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + #[idm_test] + async fn test_idm_scim_sync_refresh_ipa_example_1( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Test properties of the imported entries. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // Test properties of the imported entries. + let mut idms_prox_write = idms.proxy_write(ct).await; - let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!( - testgroup.get_ava_single_iutf8("sync_external_id") - == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + let testgroup = get_single_entry("testgroup", &mut idms_prox_write); + assert!( + testgroup.get_ava_single_iutf8("sync_external_id") + == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); - let testposix = get_single_entry("testposix", &mut idms_prox_write); - assert!( - testposix.get_ava_single_iutf8("sync_external_id") - == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); + let testposix = get_single_entry("testposix", &mut idms_prox_write); + assert!( + testposix.get_ava_single_iutf8("sync_external_id") + == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); - let testexternal = get_single_entry("testexternal", &mut idms_prox_write); - assert!( - testexternal.get_ava_single_iutf8("sync_external_id") - == Some( - "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au" - ) - ); - assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); + let testexternal = get_single_entry("testexternal", &mut idms_prox_write); + assert!( + testexternal.get_ava_single_iutf8("sync_external_id") + == Some("cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); - let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!( - testuser.get_ava_single_iutf8("sync_external_id") - == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); - assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); - assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); + let testuser = get_single_entry("testuser", &mut idms_prox_write); + assert!( + testuser.get_ava_single_iutf8("sync_external_id") + == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); + assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); + assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); - // Check memberof works. - let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); - assert!(testgroup_mb.contains(&testuser.get_uuid())); + // Check memberof works. + let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); + assert!(testgroup_mb.contains(&testuser.get_uuid())); - let testposix_mb = testposix.get_ava_refer("member").expect("No members!"); - assert!(testposix_mb.contains(&testuser.get_uuid())); + let testposix_mb = testposix.get_ava_refer("member").expect("No members!"); + assert!(testposix_mb.contains(&testuser.get_uuid())); - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); - assert!(testuser_mo.contains(&testposix.get_uuid())); - assert!(testuser_mo.contains(&testgroup.get_uuid())); + let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + assert!(testuser_mo.contains(&testposix.get_uuid())); + assert!(testuser_mo.contains(&testgroup.get_uuid())); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Now apply updates. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_2).expect("failed to parse scim sync"); + // Now apply updates. + let mut idms_prox_write = idms.proxy_write(ct).await; + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_2).expect("failed to parse scim sync"); - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // Test properties of the updated entries. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); + // Test properties of the updated entries. + let mut idms_prox_write = idms.proxy_write(ct).await; - // Deleted + // Deleted + assert!(idms_prox_write + .qs_write + .internal_search(filter!(f_eq("name", PartialValue::new_iname("testgroup")))) + .unwrap() + .is_empty()); + + let testposix = get_single_entry("testposix", &mut idms_prox_write); + info!("{:?}", testposix); + assert!( + testposix.get_ava_single_iutf8("sync_external_id") + == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); + + let testexternal = get_single_entry("testexternal2", &mut idms_prox_write); + info!("{:?}", testexternal); + assert!( + testexternal.get_ava_single_iutf8("sync_external_id") + == Some("cn=testexternal2,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); + + let testuser = get_single_entry("testuser", &mut idms_prox_write); + + // Check memberof works. + let testexternal_mb = testexternal.get_ava_refer("member").expect("No members!"); + assert!(testexternal_mb.contains(&testuser.get_uuid())); + + assert!(testposix.get_ava_refer("member").is_none()); + + let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + assert!(testuser_mo.contains(&testexternal.get_uuid())); + + assert!(idms_prox_write.commit().is_ok()); + } + + #[idm_test] + async fn test_idm_scim_sync_refresh_ipa_example_2( + 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 (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; + + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + let from_state = changes.to_state.clone(); + + // Indicate the next set of changes will be a refresh. Don't change content. + // Strictly speaking this step isn't need. + + let changes = ScimSyncRequest::need_refresh(from_state); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + // Check entries still remain as expected. + let testgroup = get_single_entry("testgroup", &mut idms_prox_write); + assert!( + testgroup.get_ava_single_iutf8("sync_external_id") + == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + + let testposix = get_single_entry("testposix", &mut idms_prox_write); + assert!( + testposix.get_ava_single_iutf8("sync_external_id") + == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); + + let testexternal = get_single_entry("testexternal", &mut idms_prox_write); + assert!( + testexternal.get_ava_single_iutf8("sync_external_id") + == Some("cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); + + let testuser = get_single_entry("testuser", &mut idms_prox_write); + assert!( + testuser.get_ava_single_iutf8("sync_external_id") + == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); + assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); + assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); + + // Check memberof works. + let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); + assert!(testgroup_mb.contains(&testuser.get_uuid())); + + let testposix_mb = testposix.get_ava_refer("member").expect("No members!"); + assert!(testposix_mb.contains(&testuser.get_uuid())); + + let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + assert!(testuser_mo.contains(&testposix.get_uuid())); + assert!(testuser_mo.contains(&testgroup.get_uuid())); + + // Now, the next change is the refresh. + + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_REFRESH_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + assert!(idms_prox_write + .qs_write + .internal_search(filter!(f_eq("name", PartialValue::new_iname("testposix")))) + .unwrap() + .is_empty()); + + assert!(idms_prox_write + .qs_write + .internal_search(filter!(f_eq( + "name", + PartialValue::new_iname("testexternal") + ))) + .unwrap() + .is_empty()); + + let testgroup = get_single_entry("testgroup", &mut idms_prox_write); + assert!( + testgroup.get_ava_single_iutf8("sync_external_id") + == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + + let testuser = get_single_entry("testuser", &mut idms_prox_write); + assert!( + testuser.get_ava_single_iutf8("sync_external_id") + == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") + ); + assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); + assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); + assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); + + // Check memberof works. + let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); + assert!(testgroup_mb.contains(&testuser.get_uuid())); + + let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + assert!(testuser_mo.contains(&testgroup.get_uuid())); + + assert!(idms_prox_write.commit().is_ok()); + } + + #[idm_test] + async fn test_idm_scim_sync_finalise_1(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 (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; + + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + assert!(idms_prox_write.commit().is_ok()); + + // Finalise the sync account. + let mut idms_prox_write = idms.proxy_write(ct).await; + + let ident = idms_prox_write + .qs_write + .internal_search_uuid(UUID_ADMIN) + .map(Identity::from_impersonate_entry_readwrite) + .expect("Failed to get admin"); + + let sfe = ScimSyncFinaliseEvent { + ident, + target: sync_uuid, + }; + + idms_prox_write + .scim_sync_finalise(&sfe) + .expect("Failed to finalise sync account"); + + // Check that the entries still exists but now have no sync_object attached. + let testgroup = get_single_entry("testgroup", &mut idms_prox_write); + assert!(!testgroup.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); + + let testposix = get_single_entry("testposix", &mut idms_prox_write); + assert!(!testposix.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); + + let testexternal = get_single_entry("testexternal", &mut idms_prox_write); + assert!(!testexternal.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); + + let testuser = get_single_entry("testuser", &mut idms_prox_write); + assert!(!testuser.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); + + assert!(idms_prox_write.commit().is_ok()); + } + + #[idm_test] + async fn test_idm_scim_sync_finalise_2(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 (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; + + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + // The difference in this test is that the refresh deletes some entries + // so the recycle bin case needs to be handled. + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_REFRESH_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + assert!(idms_prox_write.commit().is_ok()); + + // Finalise the sync account. + let mut idms_prox_write = idms.proxy_write(ct).await; + + let ident = idms_prox_write + .qs_write + .internal_search_uuid(UUID_ADMIN) + .map(Identity::from_impersonate_entry_readwrite) + .expect("Failed to get admin"); + + let sfe = ScimSyncFinaliseEvent { + ident, + target: sync_uuid, + }; + + idms_prox_write + .scim_sync_finalise(&sfe) + .expect("Failed to finalise sync account"); + + // Check that the entries still exists but now have no sync_object attached. + let testgroup = get_single_entry("testgroup", &mut idms_prox_write); + assert!(!testgroup.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); + + let testuser = get_single_entry("testuser", &mut idms_prox_write); + assert!(!testuser.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); + + for iname in ["testposix", "testexternal"] { + trace!(%iname); assert!(idms_prox_write .qs_write - .internal_search(filter!(f_eq("name", PartialValue::new_iname("testgroup")))) + .internal_search(filter!(f_eq("name", PartialValue::new_iname(iname)))) .unwrap() .is_empty()); + } - let testposix = get_single_entry("testposix", &mut idms_prox_write); - info!("{:?}", testposix); - assert!( - testposix.get_ava_single_iutf8("sync_external_id") - == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); - - let testexternal = get_single_entry("testexternal2", &mut idms_prox_write); - info!("{:?}", testexternal); - assert!( - testexternal.get_ava_single_iutf8("sync_external_id") - == Some( - "cn=testexternal2,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au" - ) - ); - assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); - - let testuser = get_single_entry("testuser", &mut idms_prox_write); - - // Check memberof works. - let testexternal_mb = testexternal.get_ava_refer("member").expect("No members!"); - assert!(testexternal_mb.contains(&testuser.get_uuid())); - - assert!(testposix.get_ava_refer("member").is_none()); - - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); - assert!(testuser_mo.contains(&testexternal.get_uuid())); - - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_scim_sync_refresh_ipa_example_2() { - 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)); - let (_sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; + #[idm_test] + async fn test_idm_scim_sync_terminate_1( + 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 (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - let from_state = changes.to_state.clone(); + assert!(idms_prox_write.commit().is_ok()); - // Indicate the next set of changes will be a refresh. Don't change content. - // Strictly speaking this step isn't need. + // Terminate the sync account + let mut idms_prox_write = idms.proxy_write(ct).await; - let changes = ScimSyncRequest::need_refresh(from_state); - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + let ident = idms_prox_write + .qs_write + .internal_search_uuid(UUID_ADMIN) + .map(Identity::from_impersonate_entry_readwrite) + .expect("Failed to get admin"); - // Check entries still remain as expected. - let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!( - testgroup.get_ava_single_iutf8("sync_external_id") - == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + let sfe = ScimSyncTerminateEvent { + ident, + target: sync_uuid, + }; - let testposix = get_single_entry("testposix", &mut idms_prox_write); - assert!( - testposix.get_ava_single_iutf8("sync_external_id") - == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); - - let testexternal = get_single_entry("testexternal", &mut idms_prox_write); - assert!( - testexternal.get_ava_single_iutf8("sync_external_id") - == Some( - "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au" - ) - ); - assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); - - let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!( - testuser.get_ava_single_iutf8("sync_external_id") - == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); - assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); - assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); - - // Check memberof works. - let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); - assert!(testgroup_mb.contains(&testuser.get_uuid())); - - let testposix_mb = testposix.get_ava_refer("member").expect("No members!"); - assert!(testposix_mb.contains(&testuser.get_uuid())); - - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); - assert!(testuser_mo.contains(&testposix.get_uuid())); - assert!(testuser_mo.contains(&testgroup.get_uuid())); - - // Now, the next change is the refresh. - - let changes = serde_json::from_str(TEST_SYNC_SCIM_IPA_REFRESH_1) - .expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + idms_prox_write + .scim_sync_terminate(&sfe) + .expect("Failed to terminate sync account"); + // Check that the entries no longer exist + for iname in ["testgroup", "testposix", "testexternal", "testuser"] { + trace!(%iname); assert!(idms_prox_write .qs_write - .internal_search(filter!(f_eq("name", PartialValue::new_iname("testposix")))) + .internal_search(filter!(f_eq("name", PartialValue::new_iname(iname)))) .unwrap() .is_empty()); + } + assert!(idms_prox_write.commit().is_ok()); + } + + #[idm_test] + async fn test_idm_scim_sync_terminate_2( + 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 (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); + let sse = ScimSyncUpdateEvent { ident }; + + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + // The difference in this test is that the refresh deletes some entries + // so the recycle bin case needs to be handled. + let changes = + serde_json::from_str(TEST_SYNC_SCIM_IPA_REFRESH_1).expect("failed to parse scim sync"); + + assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); + + assert!(idms_prox_write.commit().is_ok()); + + // Terminate the sync account + let mut idms_prox_write = idms.proxy_write(ct).await; + + let ident = idms_prox_write + .qs_write + .internal_search_uuid(UUID_ADMIN) + .map(Identity::from_impersonate_entry_readwrite) + .expect("Failed to get admin"); + + let sfe = ScimSyncTerminateEvent { + ident, + target: sync_uuid, + }; + + idms_prox_write + .scim_sync_terminate(&sfe) + .expect("Failed to terminate sync account"); + + // Check that the entries no longer exist + for iname in ["testgroup", "testposix", "testexternal", "testuser"] { + trace!(%iname); assert!(idms_prox_write .qs_write - .internal_search(filter!(f_eq( - "name", - PartialValue::new_iname("testexternal") - ))) + .internal_search(filter!(f_eq("name", PartialValue::new_iname(iname)))) .unwrap() .is_empty()); + } - let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!( - testgroup.get_ava_single_iutf8("sync_external_id") - == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); - - let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!( - testuser.get_ava_single_iutf8("sync_external_id") - == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") - ); - assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); - assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); - assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); - - // Check memberof works. - let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); - assert!(testgroup_mb.contains(&testuser.get_uuid())); - - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); - assert!(testuser_mo.contains(&testgroup.get_uuid())); - - assert!(idms_prox_write.commit().is_ok()); - }) - } - - #[test] - fn test_idm_scim_sync_finalise_1() { - 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)); - let (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; - - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - - assert!(idms_prox_write.commit().is_ok()); - - // Finalise the sync account. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - - let ident = idms_prox_write - .qs_write - .internal_search_uuid(UUID_ADMIN) - .map(Identity::from_impersonate_entry_readwrite) - .expect("Failed to get admin"); - - let sfe = ScimSyncFinaliseEvent { - ident, - target: sync_uuid, - }; - - idms_prox_write - .scim_sync_finalise(&sfe) - .expect("Failed to finalise sync account"); - - // Check that the entries still exists but now have no sync_object attached. - let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!(!testgroup.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); - - let testposix = get_single_entry("testposix", &mut idms_prox_write); - assert!(!testposix.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); - - let testexternal = get_single_entry("testexternal", &mut idms_prox_write); - assert!(!testexternal.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); - - let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!(!testuser.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); - - assert!(idms_prox_write.commit().is_ok()); - }) - } - - #[test] - fn test_idm_scim_sync_finalise_2() { - 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)); - let (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; - - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - - // The difference in this test is that the refresh deletes some entries - // so the recycle bin case needs to be handled. - let changes = serde_json::from_str(TEST_SYNC_SCIM_IPA_REFRESH_1) - .expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - - assert!(idms_prox_write.commit().is_ok()); - - // Finalise the sync account. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - - let ident = idms_prox_write - .qs_write - .internal_search_uuid(UUID_ADMIN) - .map(Identity::from_impersonate_entry_readwrite) - .expect("Failed to get admin"); - - let sfe = ScimSyncFinaliseEvent { - ident, - target: sync_uuid, - }; - - idms_prox_write - .scim_sync_finalise(&sfe) - .expect("Failed to finalise sync account"); - - // Check that the entries still exists but now have no sync_object attached. - let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!(!testgroup.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); - - let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!(!testuser.attribute_equality("class", &PVCLASS_SYNC_OBJECT)); - - for iname in ["testposix", "testexternal"] { - trace!(%iname); - assert!(idms_prox_write - .qs_write - .internal_search(filter!(f_eq("name", PartialValue::new_iname(iname)))) - .unwrap() - .is_empty()); - } - - assert!(idms_prox_write.commit().is_ok()); - }) - } - - #[test] - fn test_idm_scim_sync_terminate_1() { - 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)); - let (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; - - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - - assert!(idms_prox_write.commit().is_ok()); - - // Terminate the sync account - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - - let ident = idms_prox_write - .qs_write - .internal_search_uuid(UUID_ADMIN) - .map(Identity::from_impersonate_entry_readwrite) - .expect("Failed to get admin"); - - let sfe = ScimSyncTerminateEvent { - ident, - target: sync_uuid, - }; - - idms_prox_write - .scim_sync_terminate(&sfe) - .expect("Failed to terminate sync account"); - - // Check that the entries no longer exist - for iname in ["testgroup", "testposix", "testexternal", "testuser"] { - trace!(%iname); - assert!(idms_prox_write - .qs_write - .internal_search(filter!(f_eq("name", PartialValue::new_iname(iname)))) - .unwrap() - .is_empty()); - } - - assert!(idms_prox_write.commit().is_ok()); - }) - } - - #[test] - fn test_idm_scim_sync_terminate_2() { - 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)); - let (sync_uuid, ident) = test_scim_sync_apply_setup_ident(&mut idms_prox_write, ct); - let sse = ScimSyncUpdateEvent { ident }; - - let changes = - serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - - // The difference in this test is that the refresh deletes some entries - // so the recycle bin case needs to be handled. - let changes = serde_json::from_str(TEST_SYNC_SCIM_IPA_REFRESH_1) - .expect("failed to parse scim sync"); - - assert!(idms_prox_write.scim_sync_apply(&sse, &changes, ct).is_ok()); - - assert!(idms_prox_write.commit().is_ok()); - - // Terminate the sync account - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - - let ident = idms_prox_write - .qs_write - .internal_search_uuid(UUID_ADMIN) - .map(Identity::from_impersonate_entry_readwrite) - .expect("Failed to get admin"); - - let sfe = ScimSyncTerminateEvent { - ident, - target: sync_uuid, - }; - - idms_prox_write - .scim_sync_terminate(&sfe) - .expect("Failed to terminate sync account"); - - // Check that the entries no longer exist - for iname in ["testgroup", "testposix", "testexternal", "testuser"] { - trace!(%iname); - assert!(idms_prox_write - .qs_write - .internal_search(filter!(f_eq("name", PartialValue::new_iname(iname)))) - .unwrap() - .is_empty()); - } - - assert!(idms_prox_write.commit().is_ok()); - }) + assert!(idms_prox_write.commit().is_ok()); } const TEST_SYNC_SCIM_IPA_1: &str = r#" diff --git a/server/lib/src/idm/server.rs b/server/lib/src/idm/server.rs index b1164cef2..ff5bac38a 100644 --- a/server/lib/src/idm/server.rs +++ b/server/lib/src/idm/server.rs @@ -5,7 +5,6 @@ use std::time::Duration; use kanidm_lib_crypto::CryptoPolicy; -use async_std::task; use compact_jwt::{Jws, JwsSigner, JwsUnverified, JwsValidator}; use concread::bptree::{BptreeMap, BptreeMapReadTxn, BptreeMapWriteTxn}; use concread::cowcell::{CowCellReadTxn, CowCellWriteTxn}; @@ -144,10 +143,9 @@ pub struct IdmServerDelayed { impl IdmServer { // TODO: Make number of authsessions configurable!!! - pub fn new( + pub async fn new( qs: QueryServer, origin: &str, - // ct: Duration, ) -> Result<(IdmServer, IdmServerDelayed), OperationError> { // This is calculated back from: // 500 auths / thread -> 0.002 sec per op @@ -169,7 +167,7 @@ impl IdmServer { pw_badlist_set, oauth2rs_set, ) = { - let mut qs_read = task::block_on(qs.read()); + let mut qs_read = qs.read().await; ( qs_read.get_domain_name().to_string(), qs_read.get_domain_display_name().to_string(), @@ -264,12 +262,8 @@ impl IdmServer { self.domain_keys.read().cookie_key } - #[cfg(test)] - pub fn auth(&self) -> IdmServerAuthTransaction { - task::block_on(self.auth_async()) - } - - pub async fn auth_async(&self) -> IdmServerAuthTransaction<'_> { + /// Start an auth txn + pub async fn auth(&self) -> IdmServerAuthTransaction<'_> { let qs_read = self.qs.read().await; let mut sid = [0; 4]; @@ -320,12 +314,7 @@ impl IdmServer { } } - #[cfg(test)] - pub fn cred_update_transaction(&self) -> IdmServerCredUpdateTransaction<'_> { - task::block_on(self.cred_update_transaction_async()) - } - - pub async fn cred_update_transaction_async(&self) -> IdmServerCredUpdateTransaction<'_> { + pub async fn cred_update_transaction(&self) -> IdmServerCredUpdateTransaction<'_> { IdmServerCredUpdateTransaction { _qs_read: self.qs.read().await, // sid: Sid, @@ -2257,7 +2246,6 @@ mod tests { use std::convert::TryFrom; use std::time::Duration; - use async_std::task; use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech, AuthType, OperationError}; use smartstring::alias::String as AttrString; use time::OffsetDateTime; @@ -2282,183 +2270,166 @@ mod tests { const TEST_PASSWORD_INC: &str = "ntaoentu nkrcgaeunhibwmwmqj;k wqjbkx "; const TEST_CURRENT_TIME: u64 = 6000; - #[test] - fn test_idm_anonymous_auth() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let sid = { - // Start and test anonymous auth. - let mut idms_auth = idms.auth(); - // Send the initial auth event for initialising the session - let anon_init = AuthEvent::anonymous_init(); - // Expect success - let r1 = task::block_on( - idms_auth.auth(&anon_init, Duration::from_secs(TEST_CURRENT_TIME)), - ); - /* Some weird lifetime things happen here ... */ + #[idm_test] + async fn test_idm_anonymous_auth(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + // Start and test anonymous auth. + let mut idms_auth = idms.auth().await; + // Send the initial auth event for initialising the session + let anon_init = AuthEvent::anonymous_init(); + // Expect success + let r1 = idms_auth + .auth(&anon_init, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + /* Some weird lifetime things happen here ... */ - let sid = match r1 { - Ok(ar) => { - let AuthResult { - sessionid, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Choose(mut conts) => { - // Should only be one auth mech - assert!(conts.len() == 1); - // And it should be anonymous - let m = conts.pop().expect("Should not fail"); - assert!(m == AuthMech::Anonymous); - } - _ => { - error!( - "A critical error has occurred! We have a non-continue result!" - ); - panic!(); - } - }; - // Now pass back the sessionid, we are good to continue. - sessionid - } - Err(e) => { - // Should not occur! - error!("A critical error has occurred! {:?}", e); - panic!(); - } - }; - - debug!("sessionid is ==> {:?}", sid); - - idms_auth.commit().expect("Must not fail"); - - sid + let sid = match r1 { + Ok(ar) => { + let AuthResult { + sessionid, + state, + delay, + } = ar; + debug_assert!(delay.is_none()); + match state { + AuthState::Choose(mut conts) => { + // Should only be one auth mech + assert!(conts.len() == 1); + // And it should be anonymous + let m = conts.pop().expect("Should not fail"); + assert!(m == AuthMech::Anonymous); + } + _ => { + error!("A critical error has occurred! We have a non-continue result!"); + panic!(); + } }; - { - let mut idms_auth = idms.auth(); - let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous); + // Now pass back the sessionid, we are good to continue. + sessionid + } + Err(e) => { + // Should not occur! + error!("A critical error has occurred! {:?}", e); + panic!(); + } + }; - let r2 = task::block_on( - idms_auth.auth(&anon_begin, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); + debug!("sessionid is ==> {:?}", sid); - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; + idms_auth.commit().expect("Must not fail"); - debug_assert!(delay.is_none()); - match state { - AuthState::Continue(allowed) => { - // Check the uat. - assert!(allowed.len() == 1); - assert!(allowed.first() == Some(&AuthAllowed::Anonymous)); - } - _ => { - error!( - "A critical error has occurred! We have a non-continue result!" - ); - panic!(); - } - } - } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - // Should not occur! - panic!(); - } - }; + let mut idms_auth = idms.auth().await; + let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous); - idms_auth.commit().expect("Must not fail"); - }; - { - let mut idms_auth = idms.auth(); - // Now send the anonymous request, given the session id. - let anon_step = AuthEvent::cred_step_anonymous(sid); + let r2 = idms_auth + .auth(&anon_begin, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); - // Expect success - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); + match r2 { + Ok(ar) => { + let AuthResult { + sessionid: _, + state, + delay, + } = ar; - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - - debug_assert!(delay.is_none()); - match state { - AuthState::Success(_uat, AuthIssueSession::Token) => { - // Check the uat. - } - _ => { - error!( - "A critical error has occurred! We have a non-succcess result!" - ); - panic!(); - } - } - } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - // Should not occur! - panic!(); - } - }; - - idms_auth.commit().expect("Must not fail"); + debug_assert!(delay.is_none()); + match state { + AuthState::Continue(allowed) => { + // Check the uat. + assert!(allowed.len() == 1); + assert!(allowed.first() == Some(&AuthAllowed::Anonymous)); + } + _ => { + error!("A critical error has occurred! We have a non-continue result!"); + panic!(); + } } } - ); + Err(e) => { + error!("A critical error has occurred! {:?}", e); + // Should not occur! + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); + + let mut idms_auth = idms.auth().await; + // Now send the anonymous request, given the session id. + let anon_step = AuthEvent::cred_step_anonymous(sid); + + // Expect success + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); + + match r2 { + Ok(ar) => { + let AuthResult { + sessionid: _, + state, + delay, + } = ar; + + debug_assert!(delay.is_none()); + match state { + AuthState::Success(_uat, AuthIssueSession::Token) => { + // Check the uat. + } + _ => { + error!("A critical error has occurred! We have a non-succcess result!"); + panic!(); + } + } + } + Err(e) => { + error!("A critical error has occurred! {:?}", e); + // Should not occur! + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); } // Test sending anonymous but with no session init. - #[test] - fn test_idm_anonymous_auth_invalid_states() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - { - let mut idms_auth = idms.auth(); - let sid = Uuid::new_v4(); - let anon_step = AuthEvent::cred_step_anonymous(sid); + #[idm_test] + async fn test_idm_anonymous_auth_invalid_states( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + { + let mut idms_auth = idms.auth().await; + let sid = Uuid::new_v4(); + let anon_step = AuthEvent::cred_step_anonymous(sid); - // Expect failure - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); + // Expect failure + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); - match r2 { - Ok(_) => { - error!("Auth state machine not correctly enforced!"); - panic!(); - } - Err(e) => match e { - OperationError::InvalidSessionState => {} - _ => panic!(), - }, - }; + match r2 { + Ok(_) => { + error!("Auth state machine not correctly enforced!"); + panic!(); } - } - ) + Err(e) => match e { + OperationError::InvalidSessionState => {} + _ => panic!(), + }, + }; + } } - async fn init_admin_w_password(qs: &QueryServer, pw: &str) -> Result { + async fn init_admin_w_password(idms: &IdmServer, pw: &str) -> Result { let p = CryptoPolicy::minimum(); let cred = Credential::new_password_only(&p, pw)?; let cred_id = cred.uuid; let v_cred = Value::new_credential("primary", cred); - let mut qs_write = qs.write(duration_from_epoch_now()).await; + let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await; // now modify and provide a primary credential. let me_inv_m = unsafe { @@ -2471,16 +2442,16 @@ mod tests { ) }; // go! - assert!(qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_write.qs_write.modify(&me_inv_m).is_ok()); - qs_write.commit().map(|()| cred_id) + idms_write.commit().map(|()| cred_id) } - fn init_admin_authsession_sid(idms: &IdmServer, ct: Duration, name: &str) -> Uuid { - let mut idms_auth = idms.auth(); + async fn init_admin_authsession_sid(idms: &IdmServer, ct: Duration, name: &str) -> Uuid { + let mut idms_auth = idms.auth().await; let admin_init = AuthEvent::named_init(name); - let r1 = task::block_on(idms_auth.auth(&admin_init, ct)); + let r1 = idms_auth.auth(&admin_init, ct).await; let ar = r1.unwrap(); let AuthResult { sessionid, @@ -2494,7 +2465,7 @@ mod tests { // Now push that we want the Password Mech. let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password); - let r2 = task::block_on(idms_auth.auth(&admin_begin, ct)); + let r2 = idms_auth.auth(&admin_begin, ct).await; let ar = r2.unwrap(); let AuthResult { sessionid, @@ -2517,14 +2488,17 @@ mod tests { sessionid } - fn check_admin_password(idms: &IdmServer, pw: &str) -> String { - let sid = init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "admin"); + async fn check_admin_password(idms: &IdmServer, pw: &str) -> String { + let sid = + init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "admin").await; - let mut idms_auth = idms.auth(); + let mut idms_auth = idms.auth().await; let anon_step = AuthEvent::cred_step_password(sid, pw); // Expect success - let r2 = task::block_on(idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))); + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; debug!("r2 ==> {:?}", r2); let token = match r2 { @@ -2560,554 +2534,489 @@ mod tests { token } - #[test] - fn test_idm_simple_password_auth() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - check_admin_password(idms, TEST_PASSWORD); + #[idm_test] + async fn test_idm_simple_password_auth(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) { + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + check_admin_password(idms, TEST_PASSWORD).await; - // Clear our the session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - idms_delayed.check_is_empty_or_panic(); - } - ) + // Clear our the session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + idms_delayed.check_is_empty_or_panic(); } - #[test] - fn test_idm_simple_password_spn_auth() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); + #[idm_test] + async fn test_idm_simple_password_spn_auth( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); - let sid = init_admin_authsession_sid( - idms, - Duration::from_secs(TEST_CURRENT_TIME), - "admin@example.com", - ); + let sid = init_admin_authsession_sid( + idms, + Duration::from_secs(TEST_CURRENT_TIME), + "admin@example.com", + ) + .await; - let mut idms_auth = idms.auth(); - let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); + let mut idms_auth = idms.auth().await; + let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); - // Expect success - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); + // Expect success + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Success(_uat, AuthIssueSession::Token) => { - // Check the uat. - } - _ => { - error!( - "A critical error has occurred! We have a non-succcess result!" - ); - panic!(); - } - } + match r2 { + Ok(ar) => { + let AuthResult { + sessionid: _, + state, + delay, + } = ar; + debug_assert!(delay.is_none()); + match state { + AuthState::Success(_uat, AuthIssueSession::Token) => { + // Check the uat. } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - // Should not occur! + _ => { + error!("A critical error has occurred! We have a non-succcess result!"); panic!(); } - }; - - // Clear our the session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - idms_delayed.check_is_empty_or_panic(); - - idms_auth.commit().expect("Must not fail"); + } } - ) + Err(e) => { + error!("A critical error has occurred! {:?}", e); + // Should not occur! + panic!(); + } + }; + + // Clear our the session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + idms_delayed.check_is_empty_or_panic(); + + idms_auth.commit().expect("Must not fail"); } - #[test] - fn test_idm_simple_password_invalid() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - let sid = init_admin_authsession_sid( - idms, - Duration::from_secs(TEST_CURRENT_TIME), - "admin", - ); - let mut idms_auth = idms.auth(); - let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC); + #[idm_test] + async fn test_idm_simple_password_invalid(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + let sid = + init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "admin").await; + let mut idms_auth = idms.auth().await; + let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC); - // Expect success - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); + // Expect success + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Denied(_reason) => { - // Check the uat. - } - _ => { - error!( - "A critical error has occurred! We have a non-denied result!" - ); - panic!(); - } - } + match r2 { + Ok(ar) => { + let AuthResult { + sessionid: _, + state, + delay, + } = ar; + debug_assert!(delay.is_none()); + match state { + AuthState::Denied(_reason) => { + // Check the uat. } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - // Should not occur! + _ => { + error!("A critical error has occurred! We have a non-denied result!"); panic!(); } - }; - - idms_auth.commit().expect("Must not fail"); + } } - ) + Err(e) => { + error!("A critical error has occurred! {:?}", e); + // Should not occur! + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); } - #[test] - fn test_idm_simple_password_reset() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + #[idm_test] + async fn test_idm_simple_password_reset(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - assert!(idms_prox_write.set_account_password(&pce).is_ok()); - assert!(idms_prox_write.set_account_password(&pce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + assert!(idms_prox_write.set_account_password(&pce).is_ok()); + assert!(idms_prox_write.set_account_password(&pce).is_ok()); + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_anonymous_set_password_denied() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let pce = PasswordChangeEvent::new_internal(UUID_ANONYMOUS, TEST_PASSWORD); + #[idm_test] + async fn test_idm_anonymous_set_password_denied( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + let pce = PasswordChangeEvent::new_internal(UUID_ANONYMOUS, TEST_PASSWORD); - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - assert!(idms_prox_write.set_account_password(&pce).is_err()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + assert!(idms_prox_write.set_account_password(&pce).is_err()); + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_regenerate_radius_secret() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); + #[idm_test] + async fn test_idm_regenerate_radius_secret(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); - // Generates a new credential when none exists - let r1 = idms_prox_write - .regenerate_radius_secret(&rrse) - .expect("Failed to reset radius credential 1"); - // Regenerates and overwrites the radius credential - let r2 = idms_prox_write - .regenerate_radius_secret(&rrse) - .expect("Failed to reset radius credential 2"); - assert!(r1 != r2); - } - ) + // Generates a new credential when none exists + let r1 = idms_prox_write + .regenerate_radius_secret(&rrse) + .expect("Failed to reset radius credential 1"); + // Regenerates and overwrites the radius credential + let r2 = idms_prox_write + .regenerate_radius_secret(&rrse) + .expect("Failed to reset radius credential 2"); + assert!(r1 != r2); } - #[test] - fn test_idm_radius_secret_rejected_from_account_credential() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); + #[idm_test] + async fn test_idm_radius_secret_rejected_from_account_credential( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); - let r1 = idms_prox_write - .regenerate_radius_secret(&rrse) - .expect("Failed to reset radius credential 1"); + let r1 = idms_prox_write + .regenerate_radius_secret(&rrse) + .expect("Failed to reset radius credential 1"); - // Try and set that as the main account password, should fail. - let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, r1.as_str()); - let e = idms_prox_write.set_account_password(&pce); - assert!(e.is_err()); + // Try and set that as the main account password, should fail. + let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, r1.as_str()); + let e = idms_prox_write.set_account_password(&pce); + assert!(e.is_err()); - let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, r1.as_str()); - let e = idms_prox_write.set_unix_account_password(&pce); - assert!(e.is_err()); + let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, r1.as_str()); + let e = idms_prox_write.set_unix_account_password(&pce); + assert!(e.is_err()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_radiusauthtoken() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); - let r1 = idms_prox_write - .regenerate_radius_secret(&rrse) - .expect("Failed to reset radius credential 1"); - idms_prox_write.commit().expect("failed to commit"); + #[idm_test] + async fn test_idm_radiusauthtoken(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); + let r1 = idms_prox_write + .regenerate_radius_secret(&rrse) + .expect("Failed to reset radius credential 1"); + idms_prox_write.commit().expect("failed to commit"); - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let rate = RadiusAuthTokenEvent::new_internal(UUID_ADMIN); - let tok_r = idms_prox_read - .get_radiusauthtoken(&rate, duration_from_epoch_now()) - .expect("Failed to generate radius auth token"); + let mut idms_prox_read = idms.proxy_read().await; + let rate = RadiusAuthTokenEvent::new_internal(UUID_ADMIN); + let tok_r = idms_prox_read + .get_radiusauthtoken(&rate, duration_from_epoch_now()) + .expect("Failed to generate radius auth token"); - // view the token? - assert!(r1 == tok_r.secret); - } - ) + // view the token? + assert!(r1 == tok_r.secret); } - #[test] - fn test_idm_simple_password_reject_weak() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - // len check - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); + #[idm_test] + async fn test_idm_simple_password_reject_weak( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + // len check + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; - let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "password"); - let e = idms_prox_write.set_account_password(&pce); - assert!(e.is_err()); + let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "password"); + let e = idms_prox_write.set_account_password(&pce); + assert!(e.is_err()); - // zxcvbn check - let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "password1234"); - let e = idms_prox_write.set_account_password(&pce); - assert!(e.is_err()); + // zxcvbn check + let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "password1234"); + let e = idms_prox_write.set_account_password(&pce); + assert!(e.is_err()); - // Check the "name" checking works too (I think admin may hit a common pw rule first) - let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "admin_nta"); - let e = idms_prox_write.set_account_password(&pce); - assert!(e.is_err()); + // Check the "name" checking works too (I think admin may hit a common pw rule first) + let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "admin_nta"); + let e = idms_prox_write.set_account_password(&pce); + assert!(e.is_err()); - // Check that the demo badlist password is rejected. - let pce = PasswordChangeEvent::new_internal( - UUID_ADMIN, - "demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1", - ); - let e = idms_prox_write.set_account_password(&pce); - assert!(e.is_err()); + // Check that the demo badlist password is rejected. + let pce = PasswordChangeEvent::new_internal( + UUID_ADMIN, + "demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1", + ); + let e = idms_prox_write.set_account_password(&pce); + assert!(e.is_err()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_simple_password_reject_badlist() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); + #[idm_test] + async fn test_idm_simple_password_reject_badlist( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; - // Check that the badlist password inserted is rejected. - let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "bad@no3IBTyqHu$list"); - let e = idms_prox_write.set_account_password(&pce); - assert!(e.is_err()); + // Check that the badlist password inserted is rejected. + let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, "bad@no3IBTyqHu$list"); + let e = idms_prox_write.set_account_password(&pce); + assert!(e.is_err()); - assert!(idms_prox_write.commit().is_ok()); - } - ) + assert!(idms_prox_write.commit().is_ok()); } - #[test] - fn test_idm_unixusertoken() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - // Modify admin to have posixaccount - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - // Add a posix group that has the admin as a member. - let e: Entry = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("group")), - ("class", Value::new_class("posixgroup")), - ("name", Value::new_iname("testgroup")), - ( - "uuid", - Value::Uuid(uuid::uuid!("01609135-a1c4-43d5-966b-a28227644445")) - ), - ("description", Value::new_utf8s("testgroup")), - ( - "member", - Value::Refer(uuid::uuid!("00000000-0000-0000-0000-000000000000")) - ) - ); + #[idm_test] + async fn test_idm_unixusertoken(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + // Modify admin to have posixaccount + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + // Add a posix group that has the admin as a member. + let e: Entry = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("group")), + ("class", Value::new_class("posixgroup")), + ("name", Value::new_iname("testgroup")), + ( + "uuid", + Value::Uuid(uuid::uuid!("01609135-a1c4-43d5-966b-a28227644445")) + ), + ("description", Value::new_utf8s("testgroup")), + ( + "member", + Value::Refer(uuid::uuid!("00000000-0000-0000-0000-000000000000")) + ) + ); - let ce = CreateEvent::new_internal(vec![e]); + let ce = CreateEvent::new_internal(vec![e]); - assert!(idms_prox_write.qs_write.create(&ce).is_ok()); + assert!(idms_prox_write.qs_write.create(&ce).is_ok()); - idms_prox_write.commit().expect("failed to commit"); + idms_prox_write.commit().expect("failed to commit"); - let mut idms_prox_read = task::block_on(idms.proxy_read()); + let mut idms_prox_read = idms.proxy_read().await; - let ugte = UnixGroupTokenEvent::new_internal(uuid!( - "01609135-a1c4-43d5-966b-a28227644445" - )); - let tok_g = idms_prox_read - .get_unixgrouptoken(&ugte) - .expect("Failed to generate unix group token"); + let ugte = UnixGroupTokenEvent::new_internal(uuid!("01609135-a1c4-43d5-966b-a28227644445")); + let tok_g = idms_prox_read + .get_unixgrouptoken(&ugte) + .expect("Failed to generate unix group token"); - assert!(tok_g.name == "testgroup"); - assert!(tok_g.spn == "testgroup@example.com"); + assert!(tok_g.name == "testgroup"); + assert!(tok_g.spn == "testgroup@example.com"); - let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN); - let tok_r = idms_prox_read - .get_unixusertoken(&uute, duration_from_epoch_now()) - .expect("Failed to generate unix user token"); + let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN); + let tok_r = idms_prox_read + .get_unixusertoken(&uute, duration_from_epoch_now()) + .expect("Failed to generate unix user token"); - assert!(tok_r.name == "admin"); - assert!(tok_r.spn == "admin@example.com"); - assert!(tok_r.groups.len() == 2); - assert!(tok_r.groups[0].name == "admin"); - assert!(tok_r.groups[1].name == "testgroup"); - assert!(tok_r.valid); + assert!(tok_r.name == "admin"); + assert!(tok_r.spn == "admin@example.com"); + assert!(tok_r.groups.len() == 2); + assert!(tok_r.groups[0].name == "admin"); + assert!(tok_r.groups[1].name == "testgroup"); + assert!(tok_r.valid); - // Show we can get the admin as a unix group token too - let ugte = UnixGroupTokenEvent::new_internal(uuid!( - "00000000-0000-0000-0000-000000000000" - )); - let tok_g = idms_prox_read - .get_unixgrouptoken(&ugte) - .expect("Failed to generate unix group token"); + // Show we can get the admin as a unix group token too + let ugte = UnixGroupTokenEvent::new_internal(uuid!("00000000-0000-0000-0000-000000000000")); + let tok_g = idms_prox_read + .get_unixgrouptoken(&ugte) + .expect("Failed to generate unix group token"); - assert!(tok_g.name == "admin"); - assert!(tok_g.spn == "admin@example.com"); - } - ) + assert!(tok_g.name == "admin"); + assert!(tok_g.spn == "admin@example.com"); } - #[test] - fn test_idm_simple_unix_password_reset() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - // make the admin a valid posix account - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + #[idm_test] + async fn test_idm_simple_unix_password_reset( + idms: &IdmServer, + _idms_delayed: &IdmServerDelayed, + ) { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + // make the admin a valid posix account + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - let mut idms_auth = idms.auth(); - // Check auth verification of the password + let mut idms_auth = idms.auth().await; + // Check auth verification of the password - let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - let a1 = task::block_on( - idms_auth.auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a1 { - Ok(Some(_tok)) => {} - _ => assert!(false), - }; - // Check bad password - let uuae_bad = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD_INC); - let a2 = task::block_on( - idms_auth.auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a2 { - Ok(None) => {} - _ => assert!(false), - }; - assert!(idms_auth.commit().is_ok()); + let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + let a1 = idms_auth + .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a1 { + Ok(Some(_tok)) => {} + _ => assert!(false), + }; + // Check bad password + let uuae_bad = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD_INC); + let a2 = idms_auth + .auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a2 { + Ok(None) => {} + _ => assert!(false), + }; + assert!(idms_auth.commit().is_ok()); - // Check deleting the password - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let me_purge_up = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![Modify::Purged(AttrString::from( - "unix_password", - ))]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_purge_up).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + // Check deleting the password + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let me_purge_up = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![Modify::Purged(AttrString::from("unix_password"))]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_purge_up).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - // And auth should now fail due to the lack of PW material (note that - // softlocking WON'T kick in because the cred_uuid is gone!) - let mut idms_auth = idms.auth(); - let a3 = task::block_on( - idms_auth.auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a3 { - Ok(None) => {} - _ => assert!(false), - }; - assert!(idms_auth.commit().is_ok()); - } - ) + // And auth should now fail due to the lack of PW material (note that + // softlocking WON'T kick in because the cred_uuid is gone!) + let mut idms_auth = idms.auth().await; + let a3 = idms_auth + .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a3 { + Ok(None) => {} + _ => assert!(false), + }; + assert!(idms_auth.commit().is_ok()); } - #[test] - fn test_idm_simple_password_upgrade() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - // Assert the delayed action queue is empty - idms_delayed.check_is_empty_or_panic(); - // Setup the admin w_ an imported password. - { - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - // now modify and provide a primary credential. - let me_inv_m = unsafe { - ModifyEvent::new_internal_invalid( + #[idm_test] + async fn test_idm_simple_password_upgrade( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + // Assert the delayed action queue is empty + idms_delayed.check_is_empty_or_panic(); + // Setup the admin w_ an imported password. + { + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + // now modify and provide a primary credential. + let me_inv_m = unsafe { + ModifyEvent::new_internal_invalid( filter!(f_eq("name", PartialValue::new_iname("admin"))), ModifyList::new_list(vec![Modify::Present( AttrString::from("password_import"), Value::from("{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM") )]), ) - }; - // go! - assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - } - // Still empty - idms_delayed.check_is_empty_or_panic(); - // Do an auth, this will trigger the action to send. - check_admin_password(idms, "password"); + }; + // go! + assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_prox_write.commit().is_ok()); + } + // Still empty + idms_delayed.check_is_empty_or_panic(); + // Do an auth, this will trigger the action to send. + check_admin_password(idms, "password").await; - // process it. - let da = idms_delayed.try_recv().expect("invalid"); - // The first task is the pw upgrade - assert!(matches!(da, DelayedAction::PwUpgrade(_))); - let r = task::block_on(idms.delayed_action(duration_from_epoch_now(), da)); - // The second is the auth session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - assert!(Ok(true) == r); + // process it. + let da = idms_delayed.try_recv().expect("invalid"); + // The first task is the pw upgrade + assert!(matches!(da, DelayedAction::PwUpgrade(_))); + let r = idms.delayed_action(duration_from_epoch_now(), da).await; + // The second is the auth session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + assert!(Ok(true) == r); - // Check the admin pw still matches - check_admin_password(idms, "password"); - // Clear the next auth session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + // Check the admin pw still matches + check_admin_password(idms, "password").await; + // Clear the next auth session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - // No delayed action was queued. - idms_delayed.check_is_empty_or_panic(); - } - ) + // No delayed action was queued. + idms_delayed.check_is_empty_or_panic(); } - #[test] - fn test_idm_unix_password_upgrade() { - run_idm_test!( - |_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - // Assert the delayed action queue is empty - idms_delayed.check_is_empty_or_panic(); - // Setup the admin with an imported unix pw. - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); + #[idm_test] + async fn test_idm_unix_password_upgrade(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) { + // Assert the delayed action queue is empty + idms_delayed.check_is_empty_or_panic(); + // Setup the admin with an imported unix pw. + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; - let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM"; - let pw = Password::try_from(im_pw).expect("failed to parse"); - let cred = Credential::new_from_password(pw); - let v_cred = Value::new_credential("unix", cred); + let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM"; + let pw = Password::try_from(im_pw).expect("failed to parse"); + let cred = Credential::new_from_password(pw); + let v_cred = Value::new_credential("unix", cred); - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - Modify::Present(AttrString::from("unix_password"), v_cred), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - idms_delayed.check_is_empty_or_panic(); - // Get the auth ready. - let uuae = UnixUserAuthEvent::new_internal(UUID_ADMIN, "password"); - let mut idms_auth = idms.auth(); - let a1 = task::block_on( - idms_auth.auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a1 { - Ok(Some(_tok)) => {} - _ => assert!(false), - }; - idms_auth.commit().expect("Must not fail"); - // The upgrade was queued - // Process it. - let da = idms_delayed.try_recv().expect("invalid"); - let _r = task::block_on(idms.delayed_action(duration_from_epoch_now(), da)); - // Go again - let mut idms_auth = idms.auth(); - let a2 = task::block_on( - idms_auth.auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a2 { - Ok(Some(_tok)) => {} - _ => assert!(false), - }; - idms_auth.commit().expect("Must not fail"); - // No delayed action was queued. - idms_delayed.check_is_empty_or_panic(); - } - ) + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + Modify::Present(AttrString::from("unix_password"), v_cred), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + assert!(idms_prox_write.commit().is_ok()); + idms_delayed.check_is_empty_or_panic(); + // Get the auth ready. + let uuae = UnixUserAuthEvent::new_internal(UUID_ADMIN, "password"); + let mut idms_auth = idms.auth().await; + let a1 = idms_auth + .auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a1 { + Ok(Some(_tok)) => {} + _ => assert!(false), + }; + idms_auth.commit().expect("Must not fail"); + // The upgrade was queued + // Process it. + let da = idms_delayed.try_recv().expect("invalid"); + let _r = idms.delayed_action(duration_from_epoch_now(), da).await; + // Go again + let mut idms_auth = idms.auth().await; + let a2 = idms_auth + .auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a2 { + Ok(Some(_tok)) => {} + _ => assert!(false), + }; + idms_auth.commit().expect("Must not fail"); + // No delayed action was queued. + idms_delayed.check_is_empty_or_panic(); } // For testing the timeouts @@ -3118,8 +3027,8 @@ mod tests { const TEST_EXPIRE_TIME: u64 = TEST_CURRENT_TIME + 120; const TEST_AFTER_EXPIRY: u64 = TEST_CURRENT_TIME + 240; - async fn set_admin_valid_time(qs: &QueryServer) { - let mut qs_write = qs.write(duration_from_epoch_now()).await; + async fn set_admin_valid_time(idms: &IdmServer) { + let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await; let v_valid_from = Value::new_datetime_epoch(Duration::from_secs(TEST_VALID_FROM_TIME)); let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_EXPIRE_TIME)); @@ -3135,904 +3044,860 @@ mod tests { ) }; // go! - assert!(qs_write.modify(&me_inv_m).is_ok()); + assert!(idms_write.qs_write.modify(&me_inv_m).is_ok()); - qs_write.commit().expect("Must not fail"); + idms_write.commit().expect("Must not fail"); } - #[test] - fn test_idm_account_valid_from_expire() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| { - // Any account that is not yet valrid / expired can't auth. + #[idm_test] + async fn test_idm_account_valid_from_expire( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + // Any account that is not yet valrid / expired can't auth. - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - // Set the valid bounds high/low - // TEST_VALID_FROM_TIME/TEST_EXPIRE_TIME - task::block_on(set_admin_valid_time(qs)); + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + // Set the valid bounds high/low + // TEST_VALID_FROM_TIME/TEST_EXPIRE_TIME + set_admin_valid_time(idms).await; - let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME); - let time_high = Duration::from_secs(TEST_AFTER_EXPIRY); + let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME); + let time_high = Duration::from_secs(TEST_AFTER_EXPIRY); - let mut idms_auth = idms.auth(); - let admin_init = AuthEvent::named_init("admin"); - let r1 = task::block_on(idms_auth.auth(&admin_init, time_low)); + let mut idms_auth = idms.auth().await; + let admin_init = AuthEvent::named_init("admin"); + let r1 = idms_auth.auth(&admin_init, time_low).await; - let ar = r1.unwrap(); + let ar = r1.unwrap(); + let AuthResult { + sessionid: _, + state, + delay, + } = ar; + + debug_assert!(delay.is_none()); + match state { + AuthState::Denied(_) => {} + _ => { + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); + + // And here! + let mut idms_auth = idms.auth().await; + let admin_init = AuthEvent::named_init("admin"); + let r1 = idms_auth.auth(&admin_init, time_high).await; + + let ar = r1.unwrap(); + let AuthResult { + sessionid: _, + state, + delay, + } = ar; + + debug_assert!(delay.is_none()); + match state { + AuthState::Denied(_) => {} + _ => { + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); + } + + #[idm_test] + async fn test_idm_unix_valid_from_expire( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + // Any account that is expired can't unix auth. + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + set_admin_valid_time(idms).await; + + let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME); + let time_high = Duration::from_secs(TEST_AFTER_EXPIRY); + + // make the admin a valid posix account + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + + let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + + assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); + assert!(idms_prox_write.commit().is_ok()); + + // Now check auth when the time is too high or too low. + let mut idms_auth = idms.auth().await; + let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + + let a1 = idms_auth.auth_unix(&uuae_good, time_low).await; + // Should this actually send an error with the details? Or just silently act as + // badpw? + match a1 { + Ok(None) => {} + _ => assert!(false), + }; + + let a2 = idms_auth.auth_unix(&uuae_good, time_high).await; + match a2 { + Ok(None) => {} + _ => assert!(false), + }; + + idms_auth.commit().expect("Must not fail"); + // Also check the generated unix tokens are invalid. + let mut idms_prox_read = idms.proxy_read().await; + let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN); + + let tok_r = idms_prox_read + .get_unixusertoken(&uute, time_low) + .expect("Failed to generate unix user token"); + + assert!(tok_r.name == "admin"); + assert!(!tok_r.valid); + + let tok_r = idms_prox_read + .get_unixusertoken(&uute, time_high) + .expect("Failed to generate unix user token"); + + assert!(tok_r.name == "admin"); + assert!(!tok_r.valid); + } + + #[idm_test] + async fn test_idm_radius_valid_from_expire( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + // Any account not valid/expiry should not return + // a radius packet. + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + set_admin_valid_time(idms).await; + + let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME); + let time_high = Duration::from_secs(TEST_AFTER_EXPIRY); + + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); + let _r1 = idms_prox_write + .regenerate_radius_secret(&rrse) + .expect("Failed to reset radius credential 1"); + idms_prox_write.commit().expect("failed to commit"); + + let mut idms_prox_read = idms.proxy_read().await; + let rate = RadiusAuthTokenEvent::new_internal(UUID_ADMIN); + let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_low); + + if tok_r.is_err() { + // Ok? + } else { + assert!(false); + } + + let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_high); + + if tok_r.is_err() { + // Ok? + } else { + assert!(false); + } + } + + #[idm_test] + async fn test_idm_account_softlocking(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) { + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + + // Auth invalid, no softlock present. + let sid = + init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "admin").await; + let mut idms_auth = idms.auth().await; + let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC); + + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); + + match r2 { + Ok(ar) => { let AuthResult { sessionid: _, state, delay, } = ar; - debug_assert!(delay.is_none()); match state { - AuthState::Denied(_) => {} + AuthState::Denied(reason) => { + assert!(reason != "Account is temporarily locked"); + } _ => { + error!("A critical error has occurred! We have a non-denied result!"); panic!(); } - }; + } + } + Err(e) => { + error!("A critical error has occurred! {:?}", e); + panic!(); + } + }; + idms_auth.commit().expect("Must not fail"); - idms_auth.commit().expect("Must not fail"); + // Auth init, softlock present, count == 1, same time (so before unlock_at) + // aka Auth valid immediate, (ct < exp), autofail + // aka Auth invalid immediate, (ct < exp), autofail + let mut idms_auth = idms.auth().await; + let admin_init = AuthEvent::named_init("admin"); - // And here! - let mut idms_auth = idms.auth(); - let admin_init = AuthEvent::named_init("admin"); - let r1 = task::block_on(idms_auth.auth(&admin_init, time_high)); + let r1 = idms_auth + .auth(&admin_init, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + let ar = r1.unwrap(); + let AuthResult { + sessionid, + state, + delay: _, + } = ar; + assert!(matches!(state, AuthState::Choose(_))); - let ar = r1.unwrap(); + // Soft locks only apply once a mechanism is chosen + let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password); + + let r2 = idms_auth + .auth(&admin_begin, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + let ar = r2.unwrap(); + let AuthResult { + sessionid: _, + state, + delay, + } = ar; + + debug_assert!(delay.is_none()); + match state { + AuthState::Denied(reason) => { + assert!(reason == "Account is temporarily locked"); + } + _ => { + error!("Sessions was not denied (softlock)"); + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); + + // Auth invalid once softlock pass (count == 2, exp_at grows) + // Tested in the softlock state machine. + + // Auth valid once softlock pass, valid. Count remains. + let sid = + init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME + 2), "admin") + .await; + + let mut idms_auth = idms.auth().await; + let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); + + // Expect success + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME + 2)) + .await; + debug!("r2 ==> {:?}", r2); + + match r2 { + Ok(ar) => { let AuthResult { sessionid: _, state, delay, } = ar; - debug_assert!(delay.is_none()); match state { - AuthState::Denied(_) => {} + AuthState::Success(_uat, AuthIssueSession::Token) => { + // Check the uat. + } _ => { + error!("A critical error has occurred! We have a non-succcess result!"); panic!(); } - }; - - idms_auth.commit().expect("Must not fail"); - } - ) - } - - #[test] - fn test_idm_unix_valid_from_expire() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| { - // Any account that is expired can't unix auth. - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - task::block_on(set_admin_valid_time(qs)); - - let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME); - let time_high = Duration::from_secs(TEST_AFTER_EXPIRY); - - // make the admin a valid posix account - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - - let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - - assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - - // Now check auth when the time is too high or too low. - let mut idms_auth = idms.auth(); - let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - - let a1 = task::block_on(idms_auth.auth_unix(&uuae_good, time_low)); - // Should this actually send an error with the details? Or just silently act as - // badpw? - match a1 { - Ok(None) => {} - _ => assert!(false), - }; - - let a2 = task::block_on(idms_auth.auth_unix(&uuae_good, time_high)); - match a2 { - Ok(None) => {} - _ => assert!(false), - }; - - idms_auth.commit().expect("Must not fail"); - // Also check the generated unix tokens are invalid. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN); - - let tok_r = idms_prox_read - .get_unixusertoken(&uute, time_low) - .expect("Failed to generate unix user token"); - - assert!(tok_r.name == "admin"); - assert!(!tok_r.valid); - - let tok_r = idms_prox_read - .get_unixusertoken(&uute, time_high) - .expect("Failed to generate unix user token"); - - assert!(tok_r.name == "admin"); - assert!(!tok_r.valid); - } - ) - } - - #[test] - fn test_idm_radius_valid_from_expire() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| { - // Any account not valid/expiry should not return - // a radius packet. - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - task::block_on(set_admin_valid_time(qs)); - - let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME); - let time_high = Duration::from_secs(TEST_AFTER_EXPIRY); - - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN); - let _r1 = idms_prox_write - .regenerate_radius_secret(&rrse) - .expect("Failed to reset radius credential 1"); - idms_prox_write.commit().expect("failed to commit"); - - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let rate = RadiusAuthTokenEvent::new_internal(UUID_ADMIN); - let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_low); - - if tok_r.is_err() { - // Ok? - } else { - assert!(false); - } - - let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_high); - - if tok_r.is_err() { - // Ok? - } else { - assert!(false); } } - ) + Err(e) => { + error!("A critical error has occurred! {:?}", e); + // Should not occur! + panic!(); + } + }; + + idms_auth.commit().expect("Must not fail"); + + // Clear the auth session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + idms_delayed.check_is_empty_or_panic(); + + // Auth valid after reset at, count == 0. + // Tested in the softlock state machine. + + // Auth invalid, softlock present, count == 1 + // Auth invalid after reset at, count == 0 and then to count == 1 + // Tested in the softlock state machine. } - #[test] - fn test_idm_account_softlocking() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); + #[idm_test] + async fn test_idm_account_softlocking_interleaved( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); - // Auth invalid, no softlock present. - let sid = init_admin_authsession_sid( - idms, - Duration::from_secs(TEST_CURRENT_TIME), - "admin", - ); - let mut idms_auth = idms.auth(); - let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC); + // Start an *early* auth session. + let sid_early = + init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "admin").await; - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); + // Start a second auth session + let sid_later = + init_admin_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "admin").await; + // Get the detail wrong in sid_later. + let mut idms_auth = idms.auth().await; + let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC); - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Denied(reason) => { - assert!(reason != "Account is temporarily locked"); - } - _ => { - error!( - "A critical error has occurred! We have a non-denied result!" - ); - panic!(); - } - } - } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - panic!(); - } - }; - idms_auth.commit().expect("Must not fail"); + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); - // Auth init, softlock present, count == 1, same time (so before unlock_at) - // aka Auth valid immediate, (ct < exp), autofail - // aka Auth invalid immediate, (ct < exp), autofail - let mut idms_auth = idms.auth(); - let admin_init = AuthEvent::named_init("admin"); - - let r1 = task::block_on( - idms_auth.auth(&admin_init, Duration::from_secs(TEST_CURRENT_TIME)), - ); - let ar = r1.unwrap(); - let AuthResult { - sessionid, - state, - delay: _, - } = ar; - assert!(matches!(state, AuthState::Choose(_))); - - // Soft locks only apply once a mechanism is chosen - let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password); - - let r2 = task::block_on( - idms_auth.auth(&admin_begin, Duration::from_secs(TEST_CURRENT_TIME)), - ); - let ar = r2.unwrap(); + match r2 { + Ok(ar) => { let AuthResult { sessionid: _, state, delay, } = ar; + debug_assert!(delay.is_none()); + match state { + AuthState::Denied(reason) => { + assert!(reason != "Account is temporarily locked"); + } + _ => { + error!("A critical error has occurred! We have a non-denied result!"); + panic!(); + } + } + } + Err(e) => { + error!("A critical error has occurred! {:?}", e); + panic!(); + } + }; + idms_auth.commit().expect("Must not fail"); + // Now check that sid_early is denied due to softlock. + let mut idms_auth = idms.auth().await; + let anon_step = AuthEvent::cred_step_password(sid_early, TEST_PASSWORD); + + // Expect success + let r2 = idms_auth + .auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + debug!("r2 ==> {:?}", r2); + match r2 { + Ok(ar) => { + let AuthResult { + sessionid: _, + state, + delay, + } = ar; debug_assert!(delay.is_none()); match state { AuthState::Denied(reason) => { assert!(reason == "Account is temporarily locked"); } _ => { - error!("Sessions was not denied (softlock)"); + error!("A critical error has occurred! We have a non-denied result!"); panic!(); } - }; - - idms_auth.commit().expect("Must not fail"); - - // Auth invalid once softlock pass (count == 2, exp_at grows) - // Tested in the softlock state machine. - - // Auth valid once softlock pass, valid. Count remains. - let sid = init_admin_authsession_sid( - idms, - Duration::from_secs(TEST_CURRENT_TIME + 2), - "admin", - ); - - let mut idms_auth = idms.auth(); - let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); - - // Expect success - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME + 2)), - ); - debug!("r2 ==> {:?}", r2); - - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Success(_uat, AuthIssueSession::Token) => { - // Check the uat. - } - _ => { - error!( - "A critical error has occurred! We have a non-succcess result!" - ); - panic!(); - } - } - } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - // Should not occur! - panic!(); - } - }; - - idms_auth.commit().expect("Must not fail"); - - // Clear the auth session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - idms_delayed.check_is_empty_or_panic(); - - // Auth valid after reset at, count == 0. - // Tested in the softlock state machine. - - // Auth invalid, softlock present, count == 1 - // Auth invalid after reset at, count == 0 and then to count == 1 - // Tested in the softlock state machine. - } - ) - } - - #[test] - fn test_idm_account_softlocking_interleaved() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| { - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - - // Start an *early* auth session. - let sid_early = init_admin_authsession_sid( - idms, - Duration::from_secs(TEST_CURRENT_TIME), - "admin", - ); - - // Start a second auth session - let sid_later = init_admin_authsession_sid( - idms, - Duration::from_secs(TEST_CURRENT_TIME), - "admin", - ); - // Get the detail wrong in sid_later. - let mut idms_auth = idms.auth(); - let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC); - - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); - - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Denied(reason) => { - assert!(reason != "Account is temporarily locked"); - } - _ => { - error!( - "A critical error has occurred! We have a non-denied result!" - ); - panic!(); - } - } - } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - panic!(); - } - }; - idms_auth.commit().expect("Must not fail"); - - // Now check that sid_early is denied due to softlock. - let mut idms_auth = idms.auth(); - let anon_step = AuthEvent::cred_step_password(sid_early, TEST_PASSWORD); - - // Expect success - let r2 = task::block_on( - idms_auth.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME)), - ); - debug!("r2 ==> {:?}", r2); - match r2 { - Ok(ar) => { - let AuthResult { - sessionid: _, - state, - delay, - } = ar; - debug_assert!(delay.is_none()); - match state { - AuthState::Denied(reason) => { - assert!(reason == "Account is temporarily locked"); - } - _ => { - error!( - "A critical error has occurred! We have a non-denied result!" - ); - panic!(); - } - } - } - Err(e) => { - error!("A critical error has occurred! {:?}", e); - panic!(); - } - }; - idms_auth.commit().expect("Must not fail"); - } - ) - } - - #[test] - fn test_idm_account_unix_softlocking() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| { - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - // make the admin a valid posix account - let mut idms_prox_write = - task::block_on(idms.proxy_write(duration_from_epoch_now())); - let me_posix = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("name", PartialValue::new_iname("admin"))), - ModifyList::new_list(vec![ - Modify::Present( - AttrString::from("class"), - Value::new_class("posixaccount"), - ), - Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - - let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - - let mut idms_auth = idms.auth(); - let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); - let uuae_bad = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD_INC); - - let a2 = task::block_on( - idms_auth.auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a2 { - Ok(None) => {} - _ => assert!(false), - }; - - // Now if we immediately auth again, should fail at same time due to SL - let a1 = task::block_on( - idms_auth.auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME)), - ); - match a1 { - Ok(None) => {} - _ => assert!(false), - }; - - // And then later, works because of SL lifting. - let a1 = task::block_on( - idms_auth.auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME + 2)), - ); - match a1 { - Ok(Some(_tok)) => {} - _ => assert!(false), - }; - - assert!(idms_auth.commit().is_ok()); - } - ) - } - - #[test] - fn test_idm_jwt_uat_expiry() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let expiry = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1); - // Do an authenticate - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - let token = check_admin_password(idms, TEST_PASSWORD); - - // Clear out the queued session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - // Persist it. - let r = task::block_on(idms.delayed_action(duration_from_epoch_now(), da)); - assert!(Ok(true) == r); - idms_delayed.check_is_empty_or_panic(); - - let mut idms_prox_read = task::block_on(idms.proxy_read()); - - // Check it's valid. - idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), ct) - .expect("Failed to validate"); - - // In X time it should be INVALID - match idms_prox_read.validate_and_parse_token_to_ident(Some(token.as_str()), expiry) - { - Err(OperationError::SessionExpired) => {} - _ => assert!(false), } } - ) - } - - #[test] - fn test_idm_expired_auth_session_cleanup() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| { - let ct = Duration::from_secs(TEST_CURRENT_TIME); - let expiry_a = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1); - let expiry_b = ct + Duration::from_secs((AUTH_SESSION_EXPIRY + 1) * 2); - - let session_a = Uuid::new_v4(); - let session_b = Uuid::new_v4(); - - // We need to put the credential on the admin. - let cred_id = task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - - // Assert no sessions present - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let admin = idms_prox_read - .qs_read - .internal_search_uuid(UUID_ADMIN) - .expect("failed"); - let sessions = admin.get_ava_as_session_map("user_auth_token_session"); - assert!(sessions.is_none()); - drop(idms_prox_read); - - let da = DelayedAction::AuthSessionRecord(AuthSessionRecord { - target_uuid: UUID_ADMIN, - session_id: session_a, - cred_id, - label: "Test Session A".to_string(), - expiry: Some(OffsetDateTime::unix_epoch() + expiry_a), - issued_at: OffsetDateTime::unix_epoch() + ct, - issued_by: IdentityId::User(UUID_ADMIN), - scope: SessionScope::ReadOnly, - }); - // Persist it. - let r = task::block_on(idms.delayed_action(ct, da)); - assert!(Ok(true) == r); - - // Check it was written, and check - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let admin = idms_prox_read - .qs_read - .internal_search_uuid(UUID_ADMIN) - .expect("failed"); - let sessions = admin - .get_ava_as_session_map("user_auth_token_session") - .expect("Sessions must be present!"); - assert!(sessions.len() == 1); - let session_id_a = sessions - .keys() - .copied() - .next() - .expect("Could not access session id"); - assert!(session_id_a == session_a); - - drop(idms_prox_read); - - // When we re-auth, this is what triggers the session cleanup via the delayed action. - - let da = DelayedAction::AuthSessionRecord(AuthSessionRecord { - target_uuid: UUID_ADMIN, - session_id: session_b, - cred_id, - label: "Test Session B".to_string(), - expiry: Some(OffsetDateTime::unix_epoch() + expiry_b), - issued_at: OffsetDateTime::unix_epoch() + ct, - issued_by: IdentityId::User(UUID_ADMIN), - scope: SessionScope::ReadOnly, - }); - // Persist it. - let r = task::block_on(idms.delayed_action(expiry_a, da)); - assert!(Ok(true) == r); - - let mut idms_prox_read = task::block_on(idms.proxy_read()); - let admin = idms_prox_read - .qs_read - .internal_search_uuid(UUID_ADMIN) - .expect("failed"); - let sessions = admin - .get_ava_as_session_map("user_auth_token_session") - .expect("Sessions must be present!"); - trace!(?sessions); - assert!(sessions.len() == 1); - let session_id_b = sessions - .keys() - .copied() - .next() - .expect("Could not access session id"); - assert!(session_id_b == session_b); - - assert!(session_id_a != session_id_b); + Err(e) => { + error!("A critical error has occurred! {:?}", e); + panic!(); } - ) + }; + idms_auth.commit().expect("Must not fail"); } - #[test] - fn test_idm_account_session_validation() { - run_idm_test!( - |qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| { - use compact_jwt::{Jws, JwsUnverified}; - use kanidm_proto::v1::UserAuthToken; - use std::str::FromStr; + #[idm_test] + async fn test_idm_account_unix_softlocking( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + // make the admin a valid posix account + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present(AttrString::from("class"), Value::new_class("posixaccount")), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); - let ct = Duration::from_secs(TEST_CURRENT_TIME); + let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); + assert!(idms_prox_write.commit().is_ok()); - let post_grace = ct + GRACE_WINDOW + Duration::from_secs(1); - let expiry = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1); + let mut idms_auth = idms.auth().await; + let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); + let uuae_bad = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD_INC); - // Assert that our grace time is less than expiry, so we know the failure is due to - // this. - assert!(post_grace < expiry); + let a2 = idms_auth + .auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a2 { + Ok(None) => {} + _ => assert!(false), + }; - // Do an authenticate - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - let token = check_admin_password(idms, TEST_PASSWORD); + // Now if we immediately auth again, should fail at same time due to SL + let a1 = idms_auth + .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME)) + .await; + match a1 { + Ok(None) => {} + _ => assert!(false), + }; - // Process the session info. - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - let r = task::block_on(idms.delayed_action(ct, da)); - assert!(Ok(true) == r); + // And then later, works because of SL lifting. + let a1 = idms_auth + .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME + 2)) + .await; + match a1 { + Ok(Some(_tok)) => {} + _ => assert!(false), + }; - let uat_unverified = - JwsUnverified::from_str(&token).expect("Failed to parse apitoken"); - let uat_inner: Jws = uat_unverified - .validate_embeded() - .expect("Embedded jwk not found"); - let uat_inner = uat_inner.into_inner(); - - let mut idms_prox_read = task::block_on(idms.proxy_read()); - - // Check it's valid. - idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), ct) - .expect("Failed to validate"); - - // If the auth session record wasn't processed, this will fail. - idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), post_grace) - .expect("Failed to validate"); - - drop(idms_prox_read); - - // Mark the session as invalid now. - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let dte = - DestroySessionTokenEvent::new_internal(uat_inner.uuid, uat_inner.session_id); - assert!(idms_prox_write.account_destroy_session_token(&dte).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - - // Now check again with the session destroyed. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - - // Now, within gracewindow, it's still valid. - idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), ct) - .expect("Failed to validate"); - - // post grace, it's not valid. - match idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), post_grace) - { - Err(OperationError::SessionExpired) => {} - _ => assert!(false), - } - } - ) + assert!(idms_auth.commit().is_ok()); } - #[test] - fn test_idm_uat_claim_insertion() { - 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_jwt_uat_expiry(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let expiry = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1); + // Do an authenticate + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + let token = check_admin_password(idms, TEST_PASSWORD).await; - // get an account. - let account = idms_prox_write - .target_to_account(UUID_ADMIN) - .expect("account must exist"); + // Clear out the queued session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + // Persist it. + let r = idms.delayed_action(duration_from_epoch_now(), da).await; + assert!(Ok(true) == r); + idms_delayed.check_is_empty_or_panic(); - // Create some fake UATs, then process them and see what claims fall out šŸ„³ - let session_id = uuid::Uuid::new_v4(); + let mut idms_prox_read = idms.proxy_read().await; - // For the different auth types, check that we get the correct claims: + // Check it's valid. + idms_prox_read + .validate_and_parse_token_to_ident(Some(token.as_str()), ct) + .expect("Failed to validate"); - // == anonymous - let uat = account - .to_userauthtoken(session_id, ct, AuthType::Anonymous, None) - .expect("Unable to create uat"); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - - assert!(!ident.has_claim("authtype_anonymous")); - // Does NOT have this - assert!(!ident.has_claim("authlevel_strong")); - assert!(!ident.has_claim("authclass_single")); - assert!(!ident.has_claim("authclass_mfa")); - - // == unixpassword - let uat = account - .to_userauthtoken(session_id, ct, AuthType::UnixPassword, None) - .expect("Unable to create uat"); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - - assert!(!ident.has_claim("authtype_unixpassword")); - assert!(!ident.has_claim("authclass_single")); - // Does NOT have this - assert!(!ident.has_claim("authlevel_strong")); - assert!(!ident.has_claim("authclass_mfa")); - - // == password - let uat = account - .to_userauthtoken(session_id, ct, AuthType::Password, None) - .expect("Unable to create uat"); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - - assert!(!ident.has_claim("authtype_password")); - assert!(!ident.has_claim("authclass_single")); - // Does NOT have this - assert!(!ident.has_claim("authlevel_strong")); - assert!(!ident.has_claim("authclass_mfa")); - - // == generatedpassword - let uat = account - .to_userauthtoken(session_id, ct, AuthType::GeneratedPassword, None) - .expect("Unable to create uat"); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - - assert!(!ident.has_claim("authtype_generatedpassword")); - assert!(!ident.has_claim("authclass_single")); - assert!(!ident.has_claim("authlevel_strong")); - // Does NOT have this - assert!(!ident.has_claim("authclass_mfa")); - - // == webauthn - let uat = account - .to_userauthtoken(session_id, ct, AuthType::Passkey, None) - .expect("Unable to create uat"); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - - assert!(!ident.has_claim("authtype_webauthn")); - assert!(!ident.has_claim("authclass_single")); - assert!(!ident.has_claim("authlevel_strong")); - // Does NOT have this - assert!(!ident.has_claim("authclass_mfa")); - - // == passwordmfa - let uat = account - .to_userauthtoken(session_id, ct, AuthType::PasswordMfa, None) - .expect("Unable to create uat"); - let ident = idms_prox_write - .process_uat_to_identity(&uat, ct) - .expect("Unable to process uat"); - - assert!(!ident.has_claim("authtype_passwordmfa")); - assert!(!ident.has_claim("authlevel_strong")); - assert!(!ident.has_claim("authclass_mfa")); - // Does NOT have this - assert!(!ident.has_claim("authclass_single")); - }) + // In X time it should be INVALID + match idms_prox_read.validate_and_parse_token_to_ident(Some(token.as_str()), expiry) { + Err(OperationError::SessionExpired) => {} + _ => assert!(false), + } } - #[test] - fn test_idm_jwt_uat_token_key_reload() { - 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_expired_auth_session_cleanup( + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let expiry_a = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1); + let expiry_b = ct + Duration::from_secs((AUTH_SESSION_EXPIRY + 1) * 2); - task::block_on(init_admin_w_password(qs, TEST_PASSWORD)) - .expect("Failed to setup admin account"); - let token = check_admin_password(idms, TEST_PASSWORD); + let session_a = Uuid::new_v4(); + let session_b = Uuid::new_v4(); - // Clear the session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - idms_delayed.check_is_empty_or_panic(); + // We need to put the credential on the admin. + let cred_id = init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); - let mut idms_prox_read = task::block_on(idms.proxy_read()); + // Assert no sessions present + let mut idms_prox_read = idms.proxy_read().await; + let admin = idms_prox_read + .qs_read + .internal_search_uuid(UUID_ADMIN) + .expect("failed"); + let sessions = admin.get_ava_as_session_map("user_auth_token_session"); + assert!(sessions.is_none()); + drop(idms_prox_read); - // Check it's valid. - idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), ct) - .expect("Failed to validate"); + let da = DelayedAction::AuthSessionRecord(AuthSessionRecord { + target_uuid: UUID_ADMIN, + session_id: session_a, + cred_id, + label: "Test Session A".to_string(), + expiry: Some(OffsetDateTime::unix_epoch() + expiry_a), + issued_at: OffsetDateTime::unix_epoch() + ct, + issued_by: IdentityId::User(UUID_ADMIN), + scope: SessionScope::ReadOnly, + }); + // Persist it. + let r = idms.delayed_action(ct, da).await; + assert!(Ok(true) == r); - drop(idms_prox_read); + // Check it was written, and check + let mut idms_prox_read = idms.proxy_read().await; + let admin = idms_prox_read + .qs_read + .internal_search_uuid(UUID_ADMIN) + .expect("failed"); + let sessions = admin + .get_ava_as_session_map("user_auth_token_session") + .expect("Sessions must be present!"); + assert!(sessions.len() == 1); + let session_id_a = sessions + .keys() + .copied() + .next() + .expect("Could not access session id"); + assert!(session_id_a == session_a); - // Now reset the token_key - we can cheat and push this - // through the migrate 3 to 4 code. - // - // fernet_private_key_str - // es256_private_key_der - let mut idms_prox_write = task::block_on(idms.proxy_write(ct)); - let me_reset_tokens = unsafe { - ModifyEvent::new_internal_invalid( - filter!(f_eq("uuid", PartialValue::Uuid(UUID_DOMAIN_INFO))), - ModifyList::new_list(vec![ - Modify::Purged(AttrString::from("fernet_private_key_str")), - Modify::Purged(AttrString::from("es256_private_key_der")), - Modify::Purged(AttrString::from("domain_token_key")), - ]), - ) - }; - assert!(idms_prox_write.qs_write.modify(&me_reset_tokens).is_ok()); - assert!(idms_prox_write.commit().is_ok()); - // Check the old token is invalid, due to reload. - let new_token = check_admin_password(idms, TEST_PASSWORD); + drop(idms_prox_read); - // Clear the session record - let da = idms_delayed.try_recv().expect("invalid"); - assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); - idms_delayed.check_is_empty_or_panic(); + // When we re-auth, this is what triggers the session cleanup via the delayed action. - let mut idms_prox_read = task::block_on(idms.proxy_read()); - assert!(idms_prox_read - .validate_and_parse_token_to_ident(Some(token.as_str()), ct) - .is_err()); - // A new token will work due to the matching key. - idms_prox_read - .validate_and_parse_token_to_ident(Some(new_token.as_str()), ct) - .expect("Failed to validate"); - } - ) + let da = DelayedAction::AuthSessionRecord(AuthSessionRecord { + target_uuid: UUID_ADMIN, + session_id: session_b, + cred_id, + label: "Test Session B".to_string(), + expiry: Some(OffsetDateTime::unix_epoch() + expiry_b), + issued_at: OffsetDateTime::unix_epoch() + ct, + issued_by: IdentityId::User(UUID_ADMIN), + scope: SessionScope::ReadOnly, + }); + // Persist it. + let r = idms.delayed_action(expiry_a, da).await; + assert!(Ok(true) == r); + + let mut idms_prox_read = idms.proxy_read().await; + let admin = idms_prox_read + .qs_read + .internal_search_uuid(UUID_ADMIN) + .expect("failed"); + let sessions = admin + .get_ava_as_session_map("user_auth_token_session") + .expect("Sessions must be present!"); + trace!(?sessions); + assert!(sessions.len() == 1); + let session_id_b = sessions + .keys() + .copied() + .next() + .expect("Could not access session id"); + assert!(session_id_b == session_b); + + assert!(session_id_a != session_id_b); } - #[test] - fn test_idm_service_account_to_person() { - 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_account_session_validation( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + use compact_jwt::{Jws, JwsUnverified}; + use kanidm_proto::v1::UserAuthToken; + use std::str::FromStr; - let ident = Identity::from_internal(); - let target_uuid = Uuid::new_v4(); + let ct = Duration::from_secs(TEST_CURRENT_TIME); - // Create a service account - let e = entry_init!( - ("class", Value::new_class("object")), - ("class", Value::new_class("account")), - ("class", Value::new_class("service_account")), - ("name", Value::new_iname("testaccount")), - ("uuid", Value::Uuid(target_uuid)), - ("description", Value::new_utf8s("testaccount")), - ("displayname", Value::new_utf8s("Test Account")) - ); + let post_grace = ct + GRACE_WINDOW + Duration::from_secs(1); + let expiry = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1); - let ce = CreateEvent::new_internal(vec![e]); - let cr = idms_prox_write.qs_write.create(&ce); - assert!(cr.is_ok()); + // Assert that our grace time is less than expiry, so we know the failure is due to + // this. + assert!(post_grace < expiry); - // Do the migrate. - assert!(idms_prox_write - .service_account_into_person(&ident, target_uuid) - .is_ok()); + // Do an authenticate + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + let token = check_admin_password(idms, TEST_PASSWORD).await; - // Any checks? - }) + // Process the session info. + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + let r = idms.delayed_action(ct, da).await; + assert!(Ok(true) == r); + + let uat_unverified = JwsUnverified::from_str(&token).expect("Failed to parse apitoken"); + let uat_inner: Jws = uat_unverified + .validate_embeded() + .expect("Embedded jwk not found"); + let uat_inner = uat_inner.into_inner(); + + let mut idms_prox_read = idms.proxy_read().await; + + // Check it's valid. + idms_prox_read + .validate_and_parse_token_to_ident(Some(token.as_str()), ct) + .expect("Failed to validate"); + + // If the auth session record wasn't processed, this will fail. + idms_prox_read + .validate_and_parse_token_to_ident(Some(token.as_str()), post_grace) + .expect("Failed to validate"); + + drop(idms_prox_read); + + // Mark the session as invalid now. + let mut idms_prox_write = idms.proxy_write(ct).await; + let dte = DestroySessionTokenEvent::new_internal(uat_inner.uuid, uat_inner.session_id); + assert!(idms_prox_write.account_destroy_session_token(&dte).is_ok()); + assert!(idms_prox_write.commit().is_ok()); + + // Now check again with the session destroyed. + let mut idms_prox_read = idms.proxy_read().await; + + // Now, within gracewindow, it's still valid. + idms_prox_read + .validate_and_parse_token_to_ident(Some(token.as_str()), ct) + .expect("Failed to validate"); + + // post grace, it's not valid. + match idms_prox_read.validate_and_parse_token_to_ident(Some(token.as_str()), post_grace) { + Err(OperationError::SessionExpired) => {} + _ => assert!(false), + } + } + + #[idm_test] + async fn test_idm_uat_claim_insertion(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let mut idms_prox_write = idms.proxy_write(ct).await; + + // get an account. + let account = idms_prox_write + .target_to_account(UUID_ADMIN) + .expect("account must exist"); + + // Create some fake UATs, then process them and see what claims fall out šŸ„³ + let session_id = uuid::Uuid::new_v4(); + + // For the different auth types, check that we get the correct claims: + + // == anonymous + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Anonymous, None) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_anonymous")); + // Does NOT have this + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_single")); + assert!(!ident.has_claim("authclass_mfa")); + + // == unixpassword + let uat = account + .to_userauthtoken(session_id, ct, AuthType::UnixPassword, None) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_unixpassword")); + assert!(!ident.has_claim("authclass_single")); + // Does NOT have this + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_mfa")); + + // == password + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Password, None) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_password")); + assert!(!ident.has_claim("authclass_single")); + // Does NOT have this + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_mfa")); + + // == generatedpassword + let uat = account + .to_userauthtoken(session_id, ct, AuthType::GeneratedPassword, None) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_generatedpassword")); + assert!(!ident.has_claim("authclass_single")); + assert!(!ident.has_claim("authlevel_strong")); + // Does NOT have this + assert!(!ident.has_claim("authclass_mfa")); + + // == webauthn + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_webauthn")); + assert!(!ident.has_claim("authclass_single")); + assert!(!ident.has_claim("authlevel_strong")); + // Does NOT have this + assert!(!ident.has_claim("authclass_mfa")); + + // == passwordmfa + let uat = account + .to_userauthtoken(session_id, ct, AuthType::PasswordMfa, None) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(&uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_passwordmfa")); + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_mfa")); + // Does NOT have this + assert!(!ident.has_claim("authclass_single")); + } + + #[idm_test] + async fn test_idm_jwt_uat_token_key_reload( + idms: &IdmServer, + idms_delayed: &mut IdmServerDelayed, + ) { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + + init_admin_w_password(idms, TEST_PASSWORD) + .await + .expect("Failed to setup admin account"); + let token = check_admin_password(idms, TEST_PASSWORD).await; + + // Clear the session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + idms_delayed.check_is_empty_or_panic(); + + let mut idms_prox_read = idms.proxy_read().await; + + // Check it's valid. + idms_prox_read + .validate_and_parse_token_to_ident(Some(token.as_str()), ct) + .expect("Failed to validate"); + + drop(idms_prox_read); + + // Now reset the token_key - we can cheat and push this + // through the migrate 3 to 4 code. + // + // fernet_private_key_str + // es256_private_key_der + let mut idms_prox_write = idms.proxy_write(ct).await; + let me_reset_tokens = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("uuid", PartialValue::Uuid(UUID_DOMAIN_INFO))), + ModifyList::new_list(vec![ + Modify::Purged(AttrString::from("fernet_private_key_str")), + Modify::Purged(AttrString::from("es256_private_key_der")), + Modify::Purged(AttrString::from("domain_token_key")), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_reset_tokens).is_ok()); + assert!(idms_prox_write.commit().is_ok()); + // Check the old token is invalid, due to reload. + let new_token = check_admin_password(idms, TEST_PASSWORD).await; + + // Clear the session record + let da = idms_delayed.try_recv().expect("invalid"); + assert!(matches!(da, DelayedAction::AuthSessionRecord(_))); + idms_delayed.check_is_empty_or_panic(); + + let mut idms_prox_read = idms.proxy_read().await; + assert!(idms_prox_read + .validate_and_parse_token_to_ident(Some(token.as_str()), ct) + .is_err()); + // A new token will work due to the matching key. + idms_prox_read + .validate_and_parse_token_to_ident(Some(new_token.as_str()), ct) + .expect("Failed to validate"); + } + + #[idm_test] + async fn test_idm_service_account_to_person( + 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 ident = Identity::from_internal(); + let target_uuid = Uuid::new_v4(); + + // Create a service account + let e = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("service_account")), + ("name", Value::new_iname("testaccount")), + ("uuid", Value::Uuid(target_uuid)), + ("description", Value::new_utf8s("testaccount")), + ("displayname", Value::new_utf8s("Test Account")) + ); + + let ce = CreateEvent::new_internal(vec![e]); + let cr = idms_prox_write.qs_write.create(&ce); + assert!(cr.is_ok()); + + // Do the migrate. + assert!(idms_prox_write + .service_account_into_person(&ident, target_uuid) + .is_ok()); + + // Any checks? } } diff --git a/server/lib/src/macros.rs b/server/lib/src/macros.rs index 67723666b..3eda353c2 100644 --- a/server/lib/src/macros.rs +++ b/server/lib/src/macros.rs @@ -13,7 +13,9 @@ macro_rules! setup_test { .expect("Failed to init BE"); let qs = QueryServer::new(be, schema_outer, "example.com".to_string()); - async_std::task::block_on(qs.initialise_helper(duration_from_epoch_now())) + tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.initialise_helper(duration_from_epoch_now())) .expect("init failed!"); qs }}; @@ -34,11 +36,15 @@ macro_rules! setup_test { .expect("Failed to init BE"); let qs = QueryServer::new(be, schema_outer, "example.com".to_string()); - async_std::task::block_on(qs.initialise_helper(duration_from_epoch_now())) + tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.initialise_helper(duration_from_epoch_now())) .expect("init failed!"); if !$preload_entries.is_empty() { - let mut qs_write = async_std::task::block_on(qs.write(duration_from_epoch_now())); + let mut qs_write = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.write(duration_from_epoch_now())); qs_write .internal_create($preload_entries) .expect("Failed to preload entries"); @@ -48,30 +54,6 @@ macro_rules! setup_test { }}; } -#[cfg(test)] -macro_rules! entry_str_to_account { - ($entry_str:expr) => {{ - use std::iter::once; - - use crate::entry::{Entry, EntryInvalid, EntryNew}; - use crate::idm::account::Account; - use crate::value::Value; - - let mut e: Entry = - unsafe { Entry::unsafe_from_entry_str($entry_str).into_invalid_new() }; - // Add spn, because normally this is generated but in tests we can't. - let spn = e - .get_ava_single_iname("name") - .map(|s| Value::new_spn_str(s, "example.com")) - .expect("Failed to munge spn from name!"); - e.set_ava("spn", once(spn)); - - let e = unsafe { e.into_sealed_committed() }; - - Account::try_from_entry_no_groups(&e).expect("Account conversion failure") - }}; -} - #[cfg(test)] macro_rules! entry_to_account { ($entry:expr) => {{ @@ -95,48 +77,6 @@ macro_rules! entry_to_account { }}; } -#[cfg(test)] -macro_rules! run_idm_test_inner { - ($test_fn:expr) => {{ - #[allow(unused_imports)] - use crate::be::{Backend, BackendConfig}; - #[allow(unused_imports)] - use crate::idm::server::{IdmServer, IdmServerDelayed}; - use crate::prelude::*; - #[allow(unused_imports)] - use crate::schema::Schema; - /* - use env_logger; - ::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug"); - let _ = env_logger::builder() - .format_timestamp(None) - .format_level(false) - .is_test(true) - .try_init(); - */ - - let test_server = setup_test!(); - - let (test_idm_server, mut idms_delayed) = - IdmServer::new(test_server.clone(), "https://idm.example.com") - .expect("Failed to setup idms"); - - $test_fn(&test_server, &test_idm_server, &mut idms_delayed); - // Any needed teardown? - // Make sure there are no errors. - assert!(async_std::task::block_on(test_server.verify()).len() == 0); - idms_delayed.check_is_empty_or_panic(); - }}; -} - -#[cfg(test)] -macro_rules! run_idm_test { - ($test_fn:expr) => {{ - let _ = sketching::test_init(); - run_idm_test_inner!($test_fn); - }}; -} - // Test helpers for all plugins. // #[macro_export] #[cfg(test)] @@ -165,7 +105,9 @@ macro_rules! run_create_test { }; { - let mut qs_write = async_std::task::block_on(qs.write(duration_from_epoch_now())); + let mut qs_write = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.write(duration_from_epoch_now())); let r = qs_write.create(&ce); trace!("test result: {:?}", r); assert!(r == $expect); @@ -181,7 +123,9 @@ macro_rules! run_create_test { } // Make sure there are no errors. trace!("starting verification"); - let ver = async_std::task::block_on(qs.verify()); + let ver = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.verify()); trace!("verification -> {:?}", ver); assert!(ver.len() == 0); }}; @@ -207,7 +151,9 @@ macro_rules! run_modify_test { let qs = setup_test!($preload_entries); { - let mut qs_write = async_std::task::block_on(qs.write(duration_from_epoch_now())); + let mut qs_write = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.write(duration_from_epoch_now())); $pre_hook(&mut qs_write); qs_write.commit().expect("commit failure!"); } @@ -220,7 +166,9 @@ macro_rules! run_modify_test { }; { - let mut qs_write = async_std::task::block_on(qs.write(duration_from_epoch_now())); + let mut qs_write = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.write(duration_from_epoch_now())); let r = qs_write.modify(&me); $check(&mut qs_write); trace!("test result: {:?}", r); @@ -236,7 +184,9 @@ macro_rules! run_modify_test { } // Make sure there are no errors. trace!("starting verification"); - let ver = async_std::task::block_on(qs.verify()); + let ver = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.verify()); trace!("verification -> {:?}", ver); assert!(ver.len() == 0); }}; @@ -266,7 +216,9 @@ macro_rules! run_delete_test { }; { - let mut qs_write = async_std::task::block_on(qs.write(duration_from_epoch_now())); + let mut qs_write = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.write(duration_from_epoch_now())); let r = qs_write.delete(&de); trace!("test result: {:?}", r); $check(&mut qs_write); @@ -282,7 +234,9 @@ macro_rules! run_delete_test { } // Make sure there are no errors. trace!("starting verification"); - let ver = async_std::task::block_on(qs.verify()); + let ver = tokio::runtime::Runtime::new() + .unwrap() + .block_on(qs.verify()); trace!("verification -> {:?}", ver); assert!(ver.len() == 0); }}; diff --git a/server/lib/src/testkit.rs b/server/lib/src/testkit.rs index 9e615fc56..eb9457a16 100644 --- a/server/lib/src/testkit.rs +++ b/server/lib/src/testkit.rs @@ -63,5 +63,7 @@ pub async fn setup_idm_test() -> (IdmServer, IdmServerDelayed) { qs.initialise_helper(duration_from_epoch_now()) .await .expect("init failed!"); - IdmServer::new(qs, "https://idm.example.com").expect("Failed to setup idms") + IdmServer::new(qs, "https://idm.example.com") + .await + .expect("Failed to setup idms") }