From 4dcad60374733dce231a2f9987e6fac1dce67b82 Mon Sep 17 00:00:00 2001 From: Pando85 Date: Thu, 30 Apr 2020 16:54:46 +0200 Subject: [PATCH] Extend testing of default entries Add default entries test to apply behaviours according to `designs/default_idm_layout.rst`. Add expected behaviours for: - Users - Account managers - Group managers - Admins - People Managers - Anonymous clients - Radius servers Also, refactor `kanidmd_client` tests to separate into different files and fix some documentation typos Resolves: #108 --- designs/access_profiles_and_security.rst | 2 +- designs/default_idm_layout.rst | 10 +- kanidm_book/src/accounts_and_groups.md | 12 +- kanidm_client/tests/common.rs | 63 +++ kanidm_client/tests/default_entries.rs | 622 +++++++++++++++++++++++ kanidm_client/tests/proto_v1_test.rs | 73 +-- kanidmd/src/lib/constants/acp.rs | 3 +- 7 files changed, 706 insertions(+), 79 deletions(-) create mode 100644 kanidm_client/tests/common.rs create mode 100644 kanidm_client/tests/default_entries.rs diff --git a/designs/access_profiles_and_security.rst b/designs/access_profiles_and_security.rst index 3c44f25e3..0ceab23d2 100644 --- a/designs/access_profiles_and_security.rst +++ b/designs/access_profiles_and_security.rst @@ -150,7 +150,7 @@ requirements. An example is user Alice should only be able to create objects where the class is group, and can only name the group - they can not add members to the group. -A content requriemnt could be something such as the value an attribute can contain must conform to a +A content requirement could be something such as the value an attribute can contain must conform to a regex, IE, you can create a group of any name, except where the name contains "admin" somewhere in it's name. Arguable, this is partially possible with filtering. diff --git a/designs/default_idm_layout.rst b/designs/default_idm_layout.rst index 1457e8f81..f13fa108b 100644 --- a/designs/default_idm_layout.rst +++ b/designs/default_idm_layout.rst @@ -65,16 +65,16 @@ account. As a result, this is high access. This role importantly should NOT be able to lock or alter credentials of high access granted accounts. That must be performed by a higher privilege. -* read and write to accounts, including credentials but NOT private data (see people manager) +* read and write to accounts, including write credentials but NOT private data (see people manager) * ability to lock and unlock accounts, excluding high access members. -Group Manager +Group Managers ============= This is a role who is able to manage and create groups on the system. Note this does not include high access groups. This is intended to be for support (ie service desk) staff to help users -be added to the necesary security groups within reason. +be added to the necessary security groups within reason. * read all groups * write group but not high access @@ -109,7 +109,7 @@ for write. Due to dealing with potentially private or sensitive information, this is a "high access" account. * read private or sensitive data of persons, IE legalName -* write privare or sensitive data of persons, IE legalName +* write private or sensitive data of persons, IE legalName Remember, this role does NOT allow technical changes, IE password changes or normal technical changes. @@ -139,7 +139,7 @@ should exist by default. Due to the handling of credentials, this is a "high access" group. * Read radius credentials -* Read other needed attributes to fufil radius functions. +* Read other needed attributes to fulfil radius functions. External Account Systems ======================== diff --git a/kanidm_book/src/accounts_and_groups.md b/kanidm_book/src/accounts_and_groups.md index b9e5073a9..cc394b2dd 100644 --- a/kanidm_book/src/accounts_and_groups.md +++ b/kanidm_book/src/accounts_and_groups.md @@ -10,12 +10,12 @@ box experience possible, as well as supplying best practice examples related to systems. The system admin account (the account you recovered in the setup) has limited privileges - only to -manage high-privilege accounts and services. This is to help seperate system administration +manage high-privilege accounts and services. This is to help separate system administration from identity administration actions. An idm_admin is also provided that is only for management of accounts and groups. Both admin and idm_admin should *NOT* be used for daily activities - they exist for initial -system configuration, and for disaster recovery scenarioes. You should delegate permissions +system configuration, and for disaster recovery scenarios. You should delegate permissions as required to named user accounts instead. The majority of the provided content is privilege groups that provide rights over Kanidm @@ -42,7 +42,7 @@ We can now use the idm_admin to create initial groups and accounts. You can also use anonymous to view users and groups - note that you won't see as many fields due to the different anonymous access profile limits! - kanidm account get demo_user --name anonymous + kanidm account get demo_user --name anonymous ## Viewing Default Groups @@ -60,8 +60,8 @@ accounts security and login aspects. This includes resetting account credentials We can perform a password reset on the demo_user for example as idm_admin, who is a default member of this group. - kanidm account credential set_password demo_user --name idm_admin - kanidm self whoami --name demo_user + kanidm account credential set_password demo_user --name idm_admin + kanidm self whoami --name demo_user ## Nested Groups @@ -116,5 +116,3 @@ resources that trust Kanidm. All groups that are flagged as "idm_high_privilege" should be audited and monitored to ensure that they are not altered. - - diff --git a/kanidm_client/tests/common.rs b/kanidm_client/tests/common.rs new file mode 100644 index 000000000..b6fdd6b01 --- /dev/null +++ b/kanidm_client/tests/common.rs @@ -0,0 +1,63 @@ +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 actix::prelude::*; + +pub const ADMIN_TEST_PASSWORD: &str = "integration test admin password"; +static PORT_ALLOC: AtomicUsize = AtomicUsize::new(8080); + +// Test external behaviours of the service. + +pub 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(); +} diff --git a/kanidm_client/tests/default_entries.rs b/kanidm_client/tests/default_entries.rs new file mode 100644 index 000000000..e44e17879 --- /dev/null +++ b/kanidm_client/tests/default_entries.rs @@ -0,0 +1,622 @@ +#![deny(warnings)] +use std::collections::HashSet; + +use kanidm_client::KanidmClient; +use kanidm_proto::v1::{Filter, Modify, ModifyList}; + +mod common; +use crate::common::{run_test, ADMIN_TEST_PASSWORD}; + +static USER_READABLE_ATTRS: [&str; 9] = [ + "name", + "spn", + "displayname", + "class", + "memberof", + "uuid", + "gidnumber", + "loginshell", + "ssh_publickey", +]; +static SELF_WRITEABLE_ATTRS: [&str; 7] = [ + "name", + "displayname", + "legalname", + "radius_secret", + "primary_credential", + "ssh_publickey", + "unix_password", +]; +static DEFAULT_HP_GROUP_NAMES: [&str; 22] = [ + "idm_admins", + "system_admins", + "idm_people_manage_priv", + "idm_people_account_password_import_priv", + "idm_people_extend_priv", + "idm_people_write_priv", + "idm_people_read_priv", + "idm_group_manage_priv", + "idm_group_write_priv", + "idm_account_manage_priv", + "idm_account_write_priv", + "idm_account_read_priv", + "idm_radius_servers", + "idm_hp_account_manage_priv", + "idm_hp_account_write_priv", + "idm_hp_account_read_priv", + "idm_schema_manage_priv", + "idm_hp_group_manage_priv", + "idm_hp_group_write_priv", + "idm_acp_manage_priv", + "domain_admins", + "idm_high_privilege", +]; +static DEFAULT_NOT_HP_GROUP_NAMES: [&str; 2] = + ["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"]; + +fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) -> () { + rsclient.idm_account_create(id, "Deeeeemo").unwrap(); + + // Create group and add to user to test read attr: member_of + match rsclient.idm_group_get(&group_name).unwrap() { + Some(_) => (), + None => rsclient.idm_group_create(&group_name).unwrap(), + } + + rsclient + .idm_group_add_members(&group_name, vec![id]) + .unwrap(); +} + +fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option { + println!("writing to attribute: {}", attr); + match attr { + "radius_secret" => Some( + rsclient + .idm_account_radius_credential_regenerate(id) + .is_ok(), + ), + "primary_credential" => Some( + rsclient + .idm_account_primary_credential_set_password(id, "dsadjasiodqwjk12asdl") + .is_ok(), + ), + "ssh_publickey" => Some( + rsclient + .idm_account_post_ssh_pubkey( + id, + "k1", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0\ + L1EyR30CwoP william@amethyst", + ) + .is_ok(), + ), + "unix_password" => Some( + rsclient + .idm_account_unix_cred_put(id, "dsadjasiodqwjk12asdl") + .is_ok(), + ), + entry => { + let new_value = match entry { + "acp_receiver" => "{\"Eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000011\"]}".to_string(), + "acp_targetscope" => "{\"And\": [{\"Eq\": [\"class\",\"access_control_profile\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}".to_string(), + _ => id.to_string(), + }; + let m = ModifyList::new_list(vec![ + Modify::Purged(attr.to_string()), + Modify::Present(attr.to_string(), new_value), + ]); + let f = Filter::Eq("name".to_string(), id.to_string()); + Some(rsclient.modify(f.clone(), m.clone()).is_ok()) + } + } +} + +fn add_all_attrs(mut rsclient: &mut KanidmClient, id: &str, group_name: &str) { + // Extend with posix attrs to test read attr: gidnumber and loginshell + rsclient + .idm_group_add_members("idm_admins", vec!["admin"]) + .unwrap(); + rsclient + .idm_account_unix_extend(id, None, Some(&"/bin/bash")) + .unwrap(); + rsclient.idm_group_unix_extend(&group_name, None).unwrap(); + + // Extend with person to allow legalname + rsclient.idm_account_person_extend(id).unwrap(); + + ["ssh_publickey", "legalname", "mail"] + .iter() + .for_each(|attr| { + assert!(is_attr_writable(&rsclient, id, attr).unwrap()); + }); + + // Write radius credentials + if id != "anonymous" { + login_account(&mut rsclient, id); + let _ = rsclient + .idm_account_radius_credential_regenerate(id) + .unwrap(); + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + } +} + +fn create_user_with_all_attrs( + mut rsclient: &mut KanidmClient, + id: &str, + optional_group: Option<&str>, +) -> () { + let group_format = format!("{}_group", id); + let group_name = optional_group.unwrap_or(&group_format); + + create_user(&rsclient, id, group_name); + add_all_attrs(&mut rsclient, id, group_name); +} + +fn login_account(rsclient: &mut KanidmClient, id: &str) -> () { + rsclient + .idm_group_add_members("idm_people_account_password_import_priv", vec!["admin"]) + .unwrap(); + rsclient + .idm_group_add_members("idm_people_extend_priv", vec!["admin"]) + .unwrap(); + + rsclient + .idm_account_primary_credential_set_password(id, "eicieY7ahchaoCh0eeTa") + .unwrap(); + + let _ = rsclient.logout(); + let res = rsclient.auth_simple_password(id, "eicieY7ahchaoCh0eeTa"); + println!("{} logged in", id); + assert!(res.is_ok()); +} + +fn test_read_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], is_readable: bool) -> () { + println!("Test read to {}, is readable: {}", id, is_readable); + let rset = rsclient + .search(Filter::Eq("name".to_string(), id.to_string())) + .unwrap(); + let e = rset.first().unwrap(); + attrs + .iter() + .map(|attr| { + println!("Reading {}", attr); + match *attr { + "radius_secret" => match rsclient.idm_account_radius_credential_get(id).unwrap() { + Some(_) => true, + None => false, + }, + _ => match e.attrs.get(*attr) { + Some(_) => true, + None => false, + }, + } + }) + .for_each(|is_ok| assert!(is_ok == is_readable)); +} + +fn test_write_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], is_writeable: bool) -> () { + println!("Test write to {}, is writeable: {}", id, is_writeable); + attrs + .iter() + .map(|attr| { + println!("Writing to {}", attr); + is_attr_writable(&rsclient, id, attr).unwrap() + }) + .for_each(|is_ok| assert!(is_ok == is_writeable)); +} + +fn test_modify_group(rsclient: &KanidmClient, group_names: &[&str], is_modificable: bool) -> () { + // need user test created to be added as test part + group_names.iter().for_each(|group| { + println!("Testing group: {}", group); + ["description", "name"].iter().for_each(|attr| { + assert!(is_attr_writable(&rsclient, group, attr).unwrap() == is_modificable) + }); + assert!(rsclient.idm_group_add_members(group, vec!["test"]).is_ok() == is_modificable); + }); +} + +// Users +// - Read to all self attributes (within security constraints). +// - Write to a limited set of self attributes, such as: +// name, displayname, legalname, ssh-keys, credentials etc. +#[test] +fn test_default_entries_rbac_users() { + run_test(|mut rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + + create_user_with_all_attrs(&mut rsclient, "self_account", Some("self_group")); + create_user_with_all_attrs(&mut rsclient, "other_account", Some("other_group")); + + login_account(&mut rsclient, "self_account"); + + test_read_attrs(&rsclient, "self_account", &USER_READABLE_ATTRS, true); + test_read_attrs(&rsclient, "other_account", &USER_READABLE_ATTRS, true); + + static GROUP_READABLE_ATTRS: [&str; 5] = ["class", "name", "spn", "uuid", "member"]; + test_read_attrs(&rsclient, "self_group", &GROUP_READABLE_ATTRS, true); + test_read_attrs(&rsclient, "other_group", &GROUP_READABLE_ATTRS, true); + + static USER_SENSITIVE_ATTRS: [&str; 2] = ["legalname", "mail"]; + test_read_attrs(&rsclient, "other_account", &USER_SENSITIVE_ATTRS, false); + + static SELF_READABLE_ATTRS: [&str; 1] = ["radius_secret"]; + test_read_attrs(&rsclient, "self_account", &SELF_READABLE_ATTRS, true); + test_read_attrs(&rsclient, "other_account", &SELF_READABLE_ATTRS, false); + + test_write_attrs(&rsclient, "self_account", &SELF_WRITEABLE_ATTRS, true); + test_write_attrs(&rsclient, "other_account", &SELF_WRITEABLE_ATTRS, false); + + static NON_SELF_WRITEABLE_ATTRS: [&str; 5] = + ["spn", "class", "memberof", "gidnumber", "uuid"]; + test_write_attrs(&rsclient, "self_account", &NON_SELF_WRITEABLE_ATTRS, false); + }); +} + +// Account Managers +// read and write to accounts, including write credentials but NOT private data (see people manager) +// ability to lock and unlock accounts, excluding high access members. +#[test] +fn test_default_entries_rbac_account_managers() { + run_test(|mut rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + + create_user(&rsclient, "account_manager", "idm_account_manage_priv"); + create_user_with_all_attrs(&mut rsclient, "test", Some("test_group")); + + login_account(&mut rsclient, "account_manager"); + + test_read_attrs(&rsclient, "test", &USER_READABLE_ATTRS, true); + static ACCOUNT_MANAGER_ATTRS: [&str; 5] = [ + "name", + "displayname", + "primary_credential", + "ssh_publickey", + "mail", + ]; + test_write_attrs(&rsclient, "test", &ACCOUNT_MANAGER_ATTRS, true); + + static PRIVATE_DATA_ATTRS: [&str; 1] = ["legalname"]; + test_read_attrs(&rsclient, "test", &PRIVATE_DATA_ATTRS, false); + test_write_attrs(&rsclient, "test", &PRIVATE_DATA_ATTRS, false); + // TODO: lock and _unlock, except high access members + }); +} + +// Group Managers +// read all groups +// write group but not high access +#[test] +fn test_default_entries_rbac_group_managers() { + run_test(|mut rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + + create_user(&rsclient, "group_manager", "idm_group_manage_priv"); + // create test user without creating new groups + create_user(&rsclient, "test", "idm_admins"); + + login_account(&mut rsclient, "group_manager"); + + let default_group_names: HashSet = + [&DEFAULT_HP_GROUP_NAMES[..], &DEFAULT_NOT_HP_GROUP_NAMES[..]] + .concat() + .iter() + .map(ToString::to_string) + .collect(); + + let groups = rsclient.idm_group_list().unwrap(); + let group_names: HashSet = groups + .iter() + .map(|entry| entry.attrs.get("name").unwrap().first().unwrap()) + .cloned() + .collect(); + assert_eq!(default_group_names, group_names); + + test_modify_group(&rsclient, &DEFAULT_HP_GROUP_NAMES, false); + test_modify_group(&rsclient, &DEFAULT_NOT_HP_GROUP_NAMES, true); + + rsclient.idm_group_create("test_group").unwrap(); + rsclient + .idm_group_add_members("test_group", vec!["test"]) + .unwrap(); + assert!(is_attr_writable(&rsclient, "test_group", "description").unwrap()); + }); +} + +// Admins +// read and write access control entries. +#[test] +fn test_default_entries_rbac_admins_access_control_entries() { + run_test(|rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + static ACP_COMMON_ATTRS: [&str; 4] = + ["name", "description", "acp_receiver", "acp_targetscope"]; + static ACP_ENTRIES: [&str; 28] = [ + "idm_admins_acp_recycle_search", + "idm_admins_acp_revive", + "idm_self_acp_read", + "idm_self_acp_write", + "idm_all_acp_read", + "idm_acp_people_read_priv", + "idm_acp_people_write_priv", + "idm_acp_people_manage", + "idm_acp_people_account_password_import_priv", + "idm_acp_people_extend_priv", + "idm_acp_group_write_priv", + "idm_acp_account_read_priv", + "idm_acp_account_write_priv", + "idm_acp_account_manage", + "idm_acp_radius_servers", + "idm_acp_hp_account_read_priv", + "idm_acp_hp_account_write_priv", + "idm_acp_hp_group_write_priv", + "idm_acp_schema_write_attrs_priv", + "idm_acp_acp_manage_priv", + "idm_acp_schema_write_classes_priv", + "idm_acp_group_manage", + "idm_acp_hp_account_manage", + "idm_acp_hp_group_manage", + "idm_acp_domain_admin_priv", + "idm_acp_system_config_priv", + "idm_acp_account_unix_extend_priv", + "idm_acp_group_unix_extend_priv", + ]; + + ACP_ENTRIES.iter().for_each(|entry| { + test_read_attrs(&rsclient, entry, &ACP_COMMON_ATTRS, true); + test_write_attrs(&rsclient, entry, &ACP_COMMON_ATTRS, true); + }); + }); +} + +// read schema entries. +// TODO: write schema entries +#[test] +fn test_default_entries_rbac_admins_schema_entries() { + run_test(|rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + let default_classnames: HashSet = [ + "access_control_create", + "access_control_delete", + "access_control_modify", + "access_control_profile", + "access_control_search", + "attributetype", + "classtype", + "extensibleobject", + "memberof", + "object", + "recycled", + "system", + "system_info", + "tombstone", + "person", + "group", + "account", + "domain_info", + "posixaccount", + "posixgroup", + "system_config", + ] + .iter() + .map(ToString::to_string) + .collect(); + + let classtype_entries = rsclient.idm_schema_classtype_list().unwrap(); + let classnames: HashSet = classtype_entries + .iter() + .map(|entry| entry.attrs.get("classname").unwrap().first().unwrap()) + .cloned() + .collect(); + println!("{:?}", classnames); + + assert_eq!(default_classnames, classnames); + + let default_attributenames: HashSet = [ + "acp_create_attr", + "acp_create_class", + "acp_enable", + "acp_modify_class", + "acp_modify_presentattr", + "acp_modify_removedattr", + "acp_receiver", + "acp_search_attr", + "acp_targetscope", + "attributename", + "claim", + "class", + "classname", + "description", + "directmemberof", + "domain", + "index", + "last_modified_cid", + "may", + "member", + "memberof", + "multivalue", + "must", + "name", + "password_import", + "phantom", + "spn", + "syntax", + "systemmay", + "systemmust", + "unique", + "uuid", + "version", + "displayname", + "legalname", + "mail", + "ssh_publickey", + "primary_credential", + "radius_secret", + "domain_name", + "domain_uuid", + "domain_ssid", + "gidnumber", + "badlist_password", + "loginshell", + "unix_password", + ] + .iter() + .map(ToString::to_string) + .collect(); + + let attributename_entries = rsclient.idm_schema_attributetype_list().unwrap(); + println!("{:?}", attributename_entries); + let attributenames = attributename_entries + .iter() + .map(|entry| entry.attrs.get("attributename").unwrap().first().unwrap()) + .cloned() + .collect(); + + assert_eq!(default_attributenames, attributenames); + }); +} + +// modify all groups including high access groups. +// create new accounts (to bootstrap the system). +#[test] +fn test_default_entries_rbac_admins_group_entries() { + run_test(|rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + create_user(&rsclient, "test", "test_group"); + + let default_group_names = + [&DEFAULT_HP_GROUP_NAMES[..], &DEFAULT_NOT_HP_GROUP_NAMES[..]].concat(); + + test_modify_group(&rsclient, &default_group_names, true); + }); +} + +// modify high access accounts as an escalation for security sensitive accounts. +#[test] +fn test_default_entries_rbac_admins_ha_accounts() { + run_test(|rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + + static MAIN_ATTRS: [&str; 3] = ["name", "displayname", "primary_credential"]; + test_write_attrs(&rsclient, "idm_admin", &MAIN_ATTRS, true); + }); +} + +// recover from the recycle bin +#[test] +fn test_default_entries_rbac_admins_recycle_accounts() { + run_test(|rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + create_user(&rsclient, "test", "test_group"); + + rsclient.idm_account_delete("test").unwrap(); + rsclient.recycle_bin_revive("test").unwrap(); + + let acc = rsclient.idm_account_get("test").unwrap(); + assert!(acc.is_some()); + }); +} + +// People Managers +// read private or sensitive data of persons, IE legalName +// write private or sensitive data of persons, IE legalName +#[test] +fn test_default_entries_rbac_people_managers() { + run_test(|mut rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + + create_user(&rsclient, "read_people_manager", "idm_people_read_priv"); + create_user_with_all_attrs(&mut rsclient, "test", Some("test_group")); + + static PEOPLE_MANAGER_ATTRS: [&str; 2] = ["legalname", "mail"]; + + static TECHNICAL_ATTRS: [&str; 3] = + ["primary_credential", "radius_secret", "unix_password"]; + test_read_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true); + + login_account(&mut rsclient, "read_people_manager"); + + test_read_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true); + test_read_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false); + test_write_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, false); + test_write_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false); + + let _ = rsclient.logout(); + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + create_user(&rsclient, "write_people_manager", "idm_people_write_priv"); + login_account(&mut rsclient, "write_people_manager"); + + test_read_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true); + test_read_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false); + test_write_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true); + test_write_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false); + }); +} + +// Anonymous Clients + Everyone Else +// read memberof, unix attrs, name, displayname, class +#[test] +fn test_default_entries_rbac_anonymous_entry() { + run_test(|mut rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + create_user_with_all_attrs(&mut rsclient, "test", Some("test_group")); + rsclient + .idm_group_add_members("test_group", vec!["anonymous"]) + .unwrap(); + add_all_attrs(&mut rsclient, "anonymous", "test_group"); + + let _ = rsclient.logout(); + rsclient.auth_anonymous().unwrap(); + + test_read_attrs(&rsclient, "test", &USER_READABLE_ATTRS, true); + test_read_attrs(&rsclient, "anonymous", &USER_READABLE_ATTRS, true); + test_write_attrs(&rsclient, "test", &SELF_WRITEABLE_ATTRS, false); + test_write_attrs(&rsclient, "anonymous", &SELF_WRITEABLE_ATTRS, false); + }); +} + +// RADIUS Servers +// Read radius credentials +// Read other needed attributes to fulfil radius functions. +#[test] +fn test_default_entries_rbac_radius_servers() { + run_test(|mut rsclient: KanidmClient| { + rsclient + .auth_simple_password("admin", ADMIN_TEST_PASSWORD) + .unwrap(); + create_user(&rsclient, "radius_server", "idm_radius_servers"); + create_user_with_all_attrs(&mut rsclient, "test", Some("test_group")); + + login_account(&mut rsclient, "radius_server"); + static RADIUS_NECESSARY_ATTRS: [&str; 4] = ["name", "spn", "uuid", "radius_secret"]; + + test_read_attrs(&rsclient, "test", &USER_READABLE_ATTRS, true); + test_read_attrs(&rsclient, "test", &RADIUS_NECESSARY_ATTRS, true); + test_write_attrs(&rsclient, "test", &RADIUS_NECESSARY_ATTRS, false); + }); +} diff --git a/kanidm_client/tests/proto_v1_test.rs b/kanidm_client/tests/proto_v1_test.rs index 1e403e617..75d8efdbc 100644 --- a/kanidm_client/tests/proto_v1_test.rs +++ b/kanidm_client/tests/proto_v1_test.rs @@ -1,75 +1,18 @@ #![deny(warnings)] - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::mpsc; -use std::thread; use std::time::SystemTime; -use kanidm::config::{Configuration, IntegrationTestConfig}; -use kanidm::core::create_server_core; -use kanidm::credential::totp::TOTP; -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); -const ADMIN_TEST_PASSWORD: &str = "integration test admin password"; +use kanidm::credential::totp::TOTP; +use kanidm_client::KanidmClient; +use kanidm_proto::v1::{Entry, Filter, Modify, ModifyList}; + +mod common; +use crate::common::{run_test, ADMIN_TEST_PASSWORD}; + const ADMIN_TEST_PASSWORD_CHANGE: &str = "integration test admin new🎉"; const 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| { @@ -213,7 +156,7 @@ fn test_server_admin_change_simple_password() { }); } -// Add a test for reseting another accounts pws via the rest api +// Add a test for resetting another accounts pws via the rest api #[test] fn test_server_admin_reset_simple_password() { run_test(|rsclient: KanidmClient| { diff --git a/kanidmd/src/lib/constants/acp.rs b/kanidmd/src/lib/constants/acp.rs index 9c154cd0e..e717cf5df 100644 --- a/kanidmd/src/lib/constants/acp.rs +++ b/kanidmd/src/lib/constants/acp.rs @@ -385,7 +385,8 @@ pub const JSON_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &str = r#"{ "displayname", "description", "primary_credential", - "ssh_publickey" + "ssh_publickey", + "mail" ], "acp_create_class": [ "object", "account"