#![deny(warnings)] use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use std::thread; use kanidm::config::{Configuration, IntegrationTestConfig}; use kanidm::core::create_server_core; use kanidm_client::{KanidmClient, KanidmClientBuilder}; use kanidm_proto::v1::{Entry, Filter, Modify, ModifyList}; use actix::prelude::*; use log::debug; static PORT_ALLOC: AtomicUsize = AtomicUsize::new(8080); static ADMIN_TEST_PASSWORD: &str = "integration test admin password"; static ADMIN_TEST_PASSWORD_CHANGE: &str = "integration test admin new🎉"; static UNIX_TEST_PASSWORD: &str = "unix test user password"; // Test external behaviorus of the service. fn run_test(test_fn: fn(KanidmClient) -> ()) { // ::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug"); let _ = env_logger::builder().is_test(true).try_init(); let (tx, rx) = mpsc::channel(); let port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst); let int_config = Box::new(IntegrationTestConfig { admin_password: ADMIN_TEST_PASSWORD.to_string(), }); let mut config = Configuration::new(); config.address = format!("127.0.0.1:{}", port); config.secure_cookies = false; config.integration_test_config = Some(int_config); // Setup the config ... thread::spawn(move || { // Spawn a thread for the test runner, this should have a unique // port.... System::run(move || { create_server_core(config); // This appears to be bind random ... // let srv = srv.bind("127.0.0.1:0").unwrap(); let _ = tx.send(System::current()); }) .expect("unable to start system"); }); let sys = rx.recv().unwrap(); System::set_current(sys.clone()); // Do we need any fixtures? // Yes probably, but they'll need to be futures as well ... // later we could accept fixture as it's own future for re-use // Setup the client, and the address we selected. let addr = format!("http://127.0.0.1:{}", port); let rsclient = KanidmClientBuilder::new() .address(addr) .build() .expect("Failed to build client"); test_fn(rsclient); // We DO NOT need teardown, as sqlite is in mem // let the tables hit the floor sys.stop(); } #[test] fn test_server_create() { run_test(|rsclient: KanidmClient| { let e: Entry = serde_json::from_str( r#"{ "attrs": { "class": ["account"], "name": ["testperson"], "displayname": ["testperson"] } }"#, ) .unwrap(); // Not logged in - should fail! let res = rsclient.create(vec![e.clone()]); assert!(res.is_err()); let a_res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(a_res.is_ok()); let res = rsclient.create(vec![e]); assert!(res.is_ok()); }); } #[test] fn test_server_modify() { run_test(|rsclient: KanidmClient| { // Build a self mod. let f = Filter::SelfUUID; let m = ModifyList::new_list(vec![ Modify::Purged("displayname".to_string()), Modify::Present("displayname".to_string(), "test".to_string()), ]); // Not logged in - should fail! let res = rsclient.modify(f.clone(), m.clone()); assert!(res.is_err()); let a_res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(a_res.is_ok()); let res = rsclient.modify(f, m); println!("{:?}", res); assert!(res.is_ok()); }); } #[test] fn test_server_whoami_anonymous() { run_test(|rsclient: KanidmClient| { // First show we are un-authenticated. let pre_res = rsclient.whoami(); // This means it was okay whoami, but no uat attached. assert!(pre_res.unwrap().is_none()); // Now login as anonymous let res = rsclient.auth_anonymous(); assert!(res.is_ok()); // Now do a whoami. let (_e, uat) = match rsclient.whoami().unwrap() { Some((e, uat)) => (e, uat), None => panic!(), }; debug!("{}", uat); assert!(uat.name == "anonymous"); }); } #[test] fn test_server_whoami_admin_simple_password() { run_test(|rsclient: KanidmClient| { // First show we are un-authenticated. let pre_res = rsclient.whoami(); // This means it was okay whoami, but no uat attached. assert!(pre_res.unwrap().is_none()); let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Now do a whoami. let (_e, uat) = match rsclient.whoami().unwrap() { Some((e, uat)) => (e, uat), None => panic!(), }; debug!("{}", uat); assert!(uat.name == "admin"); }); } #[test] fn test_server_search() { run_test(|rsclient: KanidmClient| { // First show we are un-authenticated. let pre_res = rsclient.whoami(); // This means it was okay whoami, but no uat attached. assert!(pre_res.unwrap().is_none()); let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); let rset = rsclient .search(Filter::Eq("name".to_string(), "admin".to_string())) .unwrap(); println!("{:?}", rset); let e = rset.first().unwrap(); // Check it's admin. println!("{:?}", e); let name = e.attrs.get("name").unwrap(); assert!(name == &vec!["admin".to_string()]); }); } #[test] fn test_server_admin_change_simple_password() { run_test(|mut rsclient: KanidmClient| { // First show we are un-authenticated. let pre_res = rsclient.whoami(); // This means it was okay whoami, but no uat attached. assert!(pre_res.unwrap().is_none()); let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Now change the password. rsclient .idm_account_set_password(ADMIN_TEST_PASSWORD_CHANGE.to_string()) .unwrap(); // Now "reset" the client. let _ = rsclient.logout(); // Old password fails let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_err()); // New password works! let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD_CHANGE); assert!(res.is_ok()); }); } // Add a test for reseting another accounts pws via the rest api #[test] fn test_server_admin_reset_simple_password() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Create a diff account let e: Entry = serde_json::from_str( r#"{ "attrs": { "class": ["account"], "name": ["testperson"], "displayname": ["testperson"] } }"#, ) .unwrap(); // Not logged in - should fail! let res = rsclient.create(vec![e]); assert!(res.is_ok()); // By default, admin's can't actually administer accounts, so mod them into // the account admin group. let f = Filter::Eq("name".to_string(), "idm_admins".to_string()); let m = ModifyList::new_list(vec![Modify::Present( "member".to_string(), "system_admins".to_string(), )]); let res = rsclient.modify(f, m); assert!(res.is_ok()); // Now set it's password - should be rejected based on low quality let res = rsclient.idm_account_primary_credential_set_password("testperson", "password"); assert!(res.is_err()); // Set the password to ensure it's good let res = rsclient.idm_account_primary_credential_set_password( "testperson", "tai4eCohtae9aegheo3Uw0oobahVighaig6heeli", ); assert!(res.is_ok()); // Check it stuck. let tclient = rsclient.new_session().expect("failed to build new session"); assert!(tclient .auth_simple_password("testperson", "tai4eCohtae9aegheo3Uw0oobahVighaig6heeli") .is_ok()); // Generate a pw instead let res = rsclient.idm_account_primary_credential_set_generated("testperson"); assert!(res.is_ok()); let gpw = res.unwrap(); let tclient = rsclient.new_session().expect("failed to build new session"); assert!(tclient .auth_simple_password("testperson", gpw.as_str()) .is_ok()); }); } // test the rest group endpoint. #[test] fn test_server_rest_group_read() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // List the groups let g_list = rsclient.idm_group_list().unwrap(); assert!(!g_list.is_empty()); let g = rsclient.idm_group_get("idm_admins").unwrap(); assert!(g.is_some()); println!("{:?}", g); }); } #[test] fn test_server_rest_group_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // List the groups let g_list = rsclient.idm_group_list().unwrap(); assert!(!g_list.is_empty()); // Create a new group rsclient.idm_group_create("demo_group").unwrap(); // List again, ensure one more. let g_list_2 = rsclient.idm_group_list().unwrap(); assert!(g_list_2.len() > g_list.len()); // Test modifications to the group // Add a member. rsclient .idm_group_add_members("demo_group", vec!["admin"]) .unwrap(); let members = rsclient.idm_group_get_members("demo_group").unwrap(); assert!(members == Some(vec!["admin".to_string()])); // Set the list of members rsclient .idm_group_set_members("demo_group", vec!["admin", "demo_group"]) .unwrap(); let members = rsclient.idm_group_get_members("demo_group").unwrap(); assert!(members == Some(vec!["admin".to_string(), "demo_group".to_string()])); // Remove a member from the group /* rsclient .idm_group_remove_member("demo_group", "demo_group") .unwrap(); let members = rsclient.idm_group_get_members("demo_group").unwrap(); assert!(members == vec!["admin".to_string()]); */ // purge members rsclient.idm_group_purge_members("demo_group").unwrap(); let members = rsclient.idm_group_get_members("demo_group").unwrap(); assert!(members == None); // Delete the group rsclient.idm_group_delete("demo_group").unwrap(); let g_list_3 = rsclient.idm_group_list().unwrap(); assert!(g_list_3.len() == g_list.len()); // Check we can get an exact group let g = rsclient.idm_group_get("idm_admins").unwrap(); assert!(g.is_some()); println!("{:?}", g); // They should have members let members = rsclient.idm_group_get_members("idm_admins").unwrap(); println!("{:?}", members); assert!(members == Some(vec!["idm_admin".to_string()])); }); } #[test] fn test_server_rest_account_read() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // List the accounts let a_list = rsclient.idm_account_list().unwrap(); assert!(!a_list.is_empty()); let a = rsclient.idm_account_get("admin").unwrap(); assert!(a.is_some()); println!("{:?}", a); }); } #[test] fn test_server_rest_schema_read() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // List the schema let s_list = rsclient.idm_schema_list().unwrap(); assert!(!s_list.is_empty()); let a_list = rsclient.idm_schema_attributetype_list().unwrap(); assert!(!a_list.is_empty()); let c_list = rsclient.idm_schema_classtype_list().unwrap(); assert!(!c_list.is_empty()); // Get an attr/class let a = rsclient.idm_schema_attributetype_get("name").unwrap(); assert!(a.is_some()); println!("{:?}", a); let c = rsclient.idm_schema_classtype_get("account").unwrap(); assert!(c.is_some()); println!("{:?}", c); }); } // Test resetting a radius cred, and then checking/viewing it. #[test] fn test_server_radius_credential_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Should have no radius secret let n_sec = rsclient.idm_account_radius_credential_get("admin").unwrap(); assert!(n_sec.is_none()); // Set one let sec1 = rsclient .idm_account_radius_credential_regenerate("admin") .unwrap(); // Should be able to get it. let r_sec = rsclient.idm_account_radius_credential_get("admin").unwrap(); assert!(sec1 == r_sec.unwrap()); // test getting the token - we can do this as self or the radius server let r_tok = rsclient.idm_account_radius_token_get("admin").unwrap(); assert!(sec1 == r_tok.secret); assert!(r_tok.name == "admin"); // Reset it let sec2 = rsclient .idm_account_radius_credential_regenerate("admin") .unwrap(); // Should be different println!("s1 {} != s2 {}", sec1, sec2); assert!(sec1 != sec2); // Delete it let res = rsclient.idm_account_radius_credential_delete("admin"); assert!(res.is_ok()); // No secret let n_sec = rsclient.idm_account_radius_credential_get("admin").unwrap(); assert!(n_sec.is_none()); }); } #[test] fn test_server_rest_account_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // To enable the admin to actually make some of these changes, we have // to make them a people admin. NOT recommended in production! rsclient .idm_group_add_members("idm_account_write_priv", vec!["admin"]) .unwrap(); // Create a new account rsclient .idm_account_create("demo_account", "Deeeeemo") .unwrap(); // View the account rsclient.idm_account_get("demo_account").unwrap(); // change the name? rsclient .idm_account_set_displayname("demo_account", "Demo Account") .unwrap(); // Delete the account rsclient.idm_account_delete("demo_account").unwrap(); }); } #[test] fn test_server_rest_sshkey_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Get the keys, should be empty vec. let sk1 = rsclient.idm_account_get_ssh_pubkeys("admin").unwrap(); assert!(sk1.is_empty()); // idm_account_get_ssh_pubkeys // idm_account_post_ssh_pubkey // idm_account_get_ssh_pubkey // idm_account_delete_ssh_pubkey // Post an invalid key (should error) let r1 = rsclient.idm_account_post_ssh_pubkey("admin", "inv", "invalid key"); assert!(r1.is_err()); // Post a valid key let r2 = rsclient .idm_account_post_ssh_pubkey("admin", "k1", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst"); println!("{:?}", r2); assert!(r2.is_ok()); // Get, should have the key let sk2 = rsclient.idm_account_get_ssh_pubkeys("admin").unwrap(); assert!(sk2.len() == 1); // Post a valid key let r3 = rsclient .idm_account_post_ssh_pubkey("admin", "k2", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx4TpJYQjd0YI5lQIHqblIsCIK5NKVFURYS/eM3o6/Z william@amethyst"); assert!(r3.is_ok()); // Get, should have both keys. let sk3 = rsclient.idm_account_get_ssh_pubkeys("admin").unwrap(); assert!(sk3.len() == 2); // Delete a key (by tag) let r4 = rsclient.idm_account_delete_ssh_pubkey("admin", "k1"); assert!(r4.is_ok()); // Get, should have remaining key. let sk4 = rsclient.idm_account_get_ssh_pubkeys("admin").unwrap(); assert!(sk4.len() == 1); // get by tag let skn = rsclient.idm_account_get_ssh_pubkey("admin", "k2"); assert!(skn.is_ok()); assert!(skn.unwrap() == Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx4TpJYQjd0YI5lQIHqblIsCIK5NKVFURYS/eM3o6/Z william@amethyst".to_string())); }); } #[test] fn test_server_rest_domain_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); let mut dlist = rsclient.idm_domain_list().unwrap(); assert!(dlist.len() == 1); let dlocal = rsclient.idm_domain_get("domain_local").unwrap(); // There should be one, and it's the domain_local assert!(dlist.pop().unwrap().attrs == dlocal.attrs); // Change the ssid rsclient .idm_domain_set_ssid("domain_local", "new_ssid") .unwrap(); // check get and get the ssid and domain info let nssid = rsclient.idm_domain_get_ssid("domain_local").unwrap(); assert!(nssid == "new_ssid"); }); } #[test] fn test_server_rest_posix_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Not recommended in production! rsclient .idm_group_add_members("idm_admins", vec!["admin"]) .unwrap(); // Create a new account rsclient .idm_account_create("posix_account", "Posix Demo Account") .unwrap(); // Extend the account with posix attrs. rsclient .idm_account_unix_extend("posix_account", None, None) .unwrap(); // Create a group // Extend the group with posix attrs rsclient.idm_group_create("posix_group").unwrap(); rsclient .idm_group_add_members("posix_group", vec!["posix_account"]) .unwrap(); rsclient.idm_group_unix_extend("posix_group", None).unwrap(); // Open a new connection as anonymous let res = rsclient.auth_anonymous(); assert!(res.is_ok()); // Get the account by name let r = rsclient .idm_account_unix_token_get("posix_account") .unwrap(); // Get the account by gidnumber let r1 = rsclient .idm_account_unix_token_get(r.gidnumber.to_string().as_str()) .unwrap(); // get the account by spn let r2 = rsclient.idm_account_unix_token_get(r.spn.as_str()).unwrap(); // get the account by uuid let r3 = rsclient .idm_account_unix_token_get(r.uuid.as_str()) .unwrap(); println!("{:?}", r); assert!(r.name == "posix_account"); assert!(r1.name == "posix_account"); assert!(r2.name == "posix_account"); assert!(r3.name == "posix_account"); // get the group by nam let r = rsclient.idm_group_unix_token_get("posix_group").unwrap(); // Get the group by gidnumber let r1 = rsclient .idm_group_unix_token_get(r.gidnumber.to_string().as_str()) .unwrap(); // get the group spn let r2 = rsclient.idm_group_unix_token_get(r.spn.as_str()).unwrap(); // get the group by uuid let r3 = rsclient.idm_group_unix_token_get(r.uuid.as_str()).unwrap(); println!("{:?}", r); assert!(r.name == "posix_group"); assert!(r1.name == "posix_group"); assert!(r2.name == "posix_group"); assert!(r3.name == "posix_group"); }); } #[test] fn test_server_rest_posix_auth_lifecycle() { run_test(|rsclient: KanidmClient| { let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); assert!(res.is_ok()); // Get an anon connection let anon_rsclient = rsclient.new_session().unwrap(); assert!(anon_rsclient.auth_anonymous().is_ok()); // Not recommended in production! rsclient .idm_group_add_members("idm_admins", vec!["admin"]) .unwrap(); // Setup a unix user rsclient .idm_account_create("posix_account", "Posix Demo Account") .unwrap(); // Extend the account with posix attrs. rsclient .idm_account_unix_extend("posix_account", None, None) .unwrap(); // add their password (unix self) rsclient .idm_account_unix_cred_put("posix_account", UNIX_TEST_PASSWORD) .unwrap(); // attempt to verify (good, anon-conn) let r1 = anon_rsclient.idm_account_unix_cred_verify("posix_account", UNIX_TEST_PASSWORD); match r1 { Ok(Some(_tok)) => {} _ => assert!(false), }; // attempt to verify (bad, anon-conn) let r2 = anon_rsclient.idm_account_unix_cred_verify("posix_account", "ntaotnhuohtsuoehtsu"); match r2 { Ok(None) => {} _ => assert!(false), }; // lock? (admin-conn) // attempt to verify (good pw, should fail, anon-conn) // status? (self-conn) // clear password? (unix self) rsclient .idm_account_unix_cred_delete("posix_account") .unwrap(); // attempt to verify (good pw, should fail, anon-conn) let r3 = anon_rsclient.idm_account_unix_cred_verify("posix_account", UNIX_TEST_PASSWORD); match r3 { Ok(None) => {} _ => assert!(false), }; }); } // Test the self version of the radius path. // Test hitting all auth-required endpoints and assert they give unauthorized.