use core::result::Result::Err; use kanidm_client::KanidmClient; use kanidm_proto::internal::{IdentifyUserRequest, IdentifyUserResponse}; use kanidmd_lib::prelude::Attribute; use kanidmd_testkit::ADMIN_TEST_PASSWORD; use reqwest::StatusCode; static UNIVERSAL_PW: &'static str = "eicieY7ahchaoCh0eeTa"; static USER_A_NAME: &'static str = "valid_user_a"; static USER_B_NAME: &'static str = "valid_user_b"; // TEST ON ERROR OUTCOMES // These tests check that invalid requests return the expected error #[kanidmd_testkit::test] async fn test_not_authenticated(rsclient: KanidmClient) { // basically here we try a bit of all the possible combinations while unauthenticated to check it's not working setup_server(&rsclient).await; create_user(&rsclient, USER_A_NAME).await; let _ = rsclient.logout().await; let res = rsclient .idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::Start) .await; assert!( matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(reqwest::StatusCode::UNAUTHORIZED, ..))) ); let res = rsclient .idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::DisplayCode) .await; assert!( matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(reqwest::StatusCode::UNAUTHORIZED, ..))) ); let res = rsclient .idm_person_identify_user( USER_A_NAME, IdentifyUserRequest::SubmitCode { other_totp: 123456 }, ) .await; assert!( matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(reqwest::StatusCode::UNAUTHORIZED, ..))) ); } #[kanidmd_testkit::test] async fn test_non_existing_user_id(rsclient: KanidmClient) { setup_server(&rsclient).await; create_user(&rsclient, USER_A_NAME).await; create_user(&rsclient, USER_B_NAME).await; let non_existing_user = "non_existing_user"; login_with_user(&rsclient, USER_A_NAME).await; let res: Result = rsclient .idm_person_identify_user(non_existing_user, IdentifyUserRequest::Start) .await; assert!( matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(kanidm_proto::v1::OperationError::NoMatchingEntries), .. ))) ); let res = rsclient .idm_person_identify_user(non_existing_user, IdentifyUserRequest::DisplayCode) .await; assert!( matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(kanidm_proto::v1::OperationError::NoMatchingEntries), .. ))) ); let res = rsclient .idm_person_identify_user( non_existing_user, IdentifyUserRequest::SubmitCode { other_totp: 123456 }, ) .await; assert!( matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(kanidm_proto::v1::OperationError::NoMatchingEntries), .. ))) ); } // TEST ON SPECIFIC API INPUT // These tests check that given a specific input we get the expected response. // WE DON'T CHECK THE CONTENT OF THE RESPONSE, just that it's the expected one. // The api tests from here on should never return any error, as all the // error cases have already been tested in the previous section! // Each tests is named like `test_{api input}_response_{expected api output}_or_{expected api output}` #[kanidmd_testkit::test] async fn test_start_response_identity_verification_available(rsclient: KanidmClient) { setup_server(&rsclient).await; create_user(&rsclient, USER_A_NAME).await; login_with_user(&rsclient, USER_A_NAME).await; let response = rsclient .idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::Start) .await; assert!(response.is_ok()); // since we sent our own identifier here it should just tell us that we that we can use the feature assert_eq!( response.unwrap(), IdentifyUserResponse::IdentityVerificationAvailable ) } // this function tests both possible POSITIVE outcomes if we start from // `Start`, that is WaitForCode or ProvideCode #[kanidmd_testkit::test] async fn test_start_response_wait_for_code_or_provide_code(rsclient: KanidmClient) { setup_server(&rsclient).await; let user_a_uuid = create_user(&rsclient, USER_A_NAME).await; let user_b_uuid = create_user(&rsclient, USER_B_NAME).await; login_with_user(&rsclient, USER_A_NAME).await; let response = rsclient .idm_person_identify_user(USER_B_NAME, IdentifyUserRequest::Start) .await; assert!(response.is_ok()); // the person with the lowest uuid should get to input the other person's code first; dbg!(user_a_uuid.clone(), user_b_uuid.clone()); if user_a_uuid < user_b_uuid { assert_eq!(response.unwrap(), IdentifyUserResponse::WaitForCode); } else { assert!(matches!( response.unwrap(), IdentifyUserResponse::ProvideCode { .. } )) } } #[kanidmd_testkit::test] async fn test_provide_code_response_code_failure_or_provide_code(rsclient: KanidmClient) { setup_server(&rsclient).await; let user_a_uuid = create_user(&rsclient, USER_A_NAME).await; let user_b_uuid = create_user(&rsclient, USER_B_NAME).await; login_with_user(&rsclient, USER_A_NAME).await; let response = rsclient .idm_person_identify_user( USER_B_NAME, IdentifyUserRequest::SubmitCode { other_totp: 123456 }, ) .await; //if A is the first then either the code is correct and therefore we get a ProvideCode or it's wrong // and we get a CodeFailure if user_a_uuid < user_b_uuid { assert!(matches!( response.unwrap(), IdentifyUserResponse::ProvideCode { .. } | IdentifyUserResponse::CodeFailure )); } else { assert!(matches!( response.unwrap(), IdentifyUserResponse::Success | IdentifyUserResponse::CodeFailure )); } } // here we actually test the full idm flow by duplicating the server #[kanidmd_testkit::test] async fn test_full_identification_flow(rsclient: KanidmClient) { setup_server(&rsclient).await; let user_a_uuid = create_user(&rsclient, USER_A_NAME).await; let user_b_uuid = create_user(&rsclient, USER_B_NAME).await; //user A session let valid_user_a_client = rsclient; login_with_user(&valid_user_a_client, USER_A_NAME).await; //user B session let valid_user_b_client = valid_user_a_client.new_session().unwrap(); login_with_user(&valid_user_b_client, USER_B_NAME).await; // now we have to consider the two separate cases: first we address the case a has the lowest uuid let (lower_user_client, lower_user_name, higher_user_client, higher_user_name) = if user_a_uuid < user_b_uuid { ( valid_user_a_client, USER_A_NAME, valid_user_b_client, USER_B_NAME, ) } else { ( valid_user_b_client, USER_B_NAME, valid_user_a_client, USER_A_NAME, ) }; let lower_user_req_1 = lower_user_client .idm_person_identify_user(higher_user_name, IdentifyUserRequest::Start) .await .unwrap(); let higher_user_req_1 = higher_user_client .idm_person_identify_user(lower_user_name, IdentifyUserRequest::Start) .await .unwrap(); assert_eq!(lower_user_req_1, IdentifyUserResponse::WaitForCode); // we check that the user A got a WaitForCode let IdentifyUserResponse::ProvideCode { step: _, totp } = higher_user_req_1 else { return assert!(false); // we check that the user B got the code }; // we now try to submit the wrong code and we check that we get CodeFailure // we now submit the received totp as the user A let lower_user_req_2_wrong = lower_user_client .idm_person_identify_user( higher_user_name, IdentifyUserRequest::SubmitCode { other_totp: totp + 1, }, ) .await .unwrap(); assert_eq!(lower_user_req_2_wrong, IdentifyUserResponse::CodeFailure); // now we do it using the right totp let lower_user_req_2_right = lower_user_client .idm_person_identify_user( higher_user_name, IdentifyUserRequest::SubmitCode { other_totp: totp }, ) .await .unwrap(); // if the totp was correct we must get a ProvideCode let IdentifyUserResponse::ProvideCode { step: _, totp } = lower_user_req_2_right else { return assert!(false); }; // we now try to do the same thing with user B: we first submit the wrong code expecting CodeFailure, // and then we submit the right one expecting Success let higher_user_req_2_wrong = higher_user_client .idm_person_identify_user( lower_user_name, IdentifyUserRequest::SubmitCode { other_totp: totp + 1, }, ) .await .unwrap(); assert_eq!(higher_user_req_2_wrong, IdentifyUserResponse::CodeFailure); // now we do it using the right totp let higher_user_req_2_right = higher_user_client .idm_person_identify_user( lower_user_name, IdentifyUserRequest::SubmitCode { other_totp: totp }, ) .await .unwrap(); // since user B has already provided their code this is their last action and they must get a Success if // the provided code is correct assert_eq!(higher_user_req_2_right, IdentifyUserResponse::Success); } async fn setup_server(rsclient: &KanidmClient) { // basically this function logs in let res = rsclient .auth_simple_password("admin", ADMIN_TEST_PASSWORD) .await; assert!(res.is_ok()); } async fn create_user(rsclient: &KanidmClient, user: &str) -> String { rsclient .idm_person_account_create(user, &format!("dx{}", user)) .await .expect("Unable to create person"); rsclient .idm_person_account_primary_credential_set_password(user, UNIVERSAL_PW) .await .unwrap(); let r = rsclient .idm_person_account_get_attr(user, Attribute::Uuid.as_ref()) .await .unwrap(); r.unwrap().first().unwrap().to_owned() } async fn login_with_user(rsclient: &KanidmClient, id: &str) { let _ = rsclient.logout().await; let res = rsclient.auth_simple_password(id, UNIVERSAL_PW).await; assert!(res.is_ok()); }