kanidm/kanidm_client/tests/default_entries.rs
Euan Kemp 0c3ce226cf
Add 'account person set' command (#667)
* Add 'account person set' command

This command allows a user to modify, say, their legal name in a
self-service fashion.

This wasn't possible before by default since the 'extend' operation
required additional ACPs in order to operate which not every user would
have.

The new "person set" api is compatible with the default self_write ACP,
and so allows self-service modification.

* Add a short section on people attributes to the book
2022-04-02 13:24:07 +10:00

633 lines
22 KiB
Rust

#![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, ADMIN_TEST_USER};
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; 24] = [
"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_hp_account_unix_extend_priv",
"idm_schema_manage_priv",
"idm_hp_group_manage_priv",
"idm_hp_group_write_priv",
"idm_hp_group_unix_extend_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, id).unwrap();
// Create group and add to user to test read attr: member_of
if rsclient.idm_group_get(&group_name).unwrap().is_none() {
rsclient.idm_group_create(&group_name).unwrap();
}
rsclient.idm_group_add_members(&group_name, &[id]).unwrap();
}
fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option<bool> {
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(),
),
"legalname" => Some(
rsclient
.idm_account_person_set(id, None, Some("test legal name".into()))
.is_ok(),
),
"mail" => Some(
rsclient
.idm_account_person_set(id, Some(&[format!("{}@example.com", id)]), None)
.is_ok(),
),
entry => {
let new_value = match entry {
"acp_receiver" => r#"{"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(rsclient: &KanidmClient, id: &str, group_name: &str) {
// Extend with posix attrs to test read attr: gidnumber and loginshell
rsclient
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
.unwrap();
rsclient
.idm_account_unix_extend(id, None, Some(&"/bin/sh"))
.unwrap();
rsclient.idm_group_unix_extend(&group_name, None).unwrap();
// Extend with person to allow legalname
rsclient.idm_account_person_extend(id, None, None).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(&rsclient, id);
let _ = rsclient
.idm_account_radius_credential_regenerate(id)
.unwrap();
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
}
}
fn create_user_with_all_attrs(
rsclient: &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(&rsclient, id, group_name);
}
fn login_account(rsclient: &KanidmClient, id: &str) -> () {
rsclient
.idm_group_add_members(
"idm_people_account_password_import_priv",
&[ADMIN_TEST_USER],
)
.unwrap();
rsclient
.idm_group_add_members("idm_people_extend_priv", &[ADMIN_TEST_USER])
.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" => rsclient
.idm_account_radius_credential_get(id)
.unwrap()
.is_some(),
_ => e.attrs.get(*attr).is_some(),
}
})
.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, &["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(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
create_user_with_all_attrs(&rsclient, "self_account", Some("self_group"));
create_user_with_all_attrs(&rsclient, "other_account", Some("other_group"));
login_account(&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(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
create_user(&rsclient, "account_manager", "idm_account_manage_priv");
create_user_with_all_attrs(&rsclient, "test", Some("test_group"));
login_account(&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 #59: 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(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, 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(&rsclient, "group_manager");
let default_group_names: HashSet<String> =
[&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<String> = groups
.iter()
.map(|entry| entry.attrs.get("name").unwrap().first().unwrap())
.cloned()
.collect();
assert!(default_group_names.is_subset(&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", &["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_TEST_USER, 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 #252: write schema entries
#[test]
fn test_default_entries_rbac_admins_schema_entries() {
run_test(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
let default_classnames: HashSet<String> = [
"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<String> = classtype_entries
.iter()
.map(|entry| entry.attrs.get("classname").unwrap().first().unwrap())
.cloned()
.collect();
println!("{:?}", classnames);
assert!(default_classnames.is_subset(&classnames));
let default_attributenames: HashSet<String> = [
"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",
"nsuniqueid",
]
.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!(default_attributenames.is_subset(&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_TEST_USER, 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_TEST_USER, 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_TEST_USER, 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(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
create_user(&rsclient, "read_people_manager", "idm_people_read_priv");
create_user_with_all_attrs(&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(&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_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
create_user(&rsclient, "write_people_manager", "idm_people_write_priv");
login_account(&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(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
create_user_with_all_attrs(&rsclient, "test", Some("test_group"));
rsclient
.idm_group_add_members("test_group", &["anonymous"])
.unwrap();
add_all_attrs(&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(|rsclient: KanidmClient| {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.unwrap();
create_user(&rsclient, "radius_server", "idm_radius_servers");
create_user_with_all_attrs(&rsclient, "test", Some("test_group"));
login_account(&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);
});
}