mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
1242 lines
38 KiB
Rust
1242 lines
38 KiB
Rust
#![deny(warnings)]
|
|
use std::future::Future;
|
|
use std::pin::Pin;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, SystemTime};
|
|
use time::OffsetDateTime;
|
|
|
|
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
|
use kanidm_proto::constants::ATTR_ACCOUNT_EXPIRE;
|
|
use kanidm_unix_common::constants::{
|
|
DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX,
|
|
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
|
|
};
|
|
use kanidm_unix_common::unix_passwd::{CryptPw, EtcGroup, EtcShadow, EtcUser};
|
|
use kanidm_unix_resolver::db::{Cache, Db};
|
|
use kanidm_unix_resolver::idprovider::interface::Id;
|
|
use kanidm_unix_resolver::idprovider::kanidm::KanidmProvider;
|
|
use kanidm_unix_resolver::idprovider::system::SystemProvider;
|
|
use kanidm_unix_resolver::resolver::Resolver;
|
|
use kanidm_unix_resolver::unix_config::{GroupMap, KanidmConfig};
|
|
use kanidmd_core::config::{Configuration, IntegrationTestConfig, ServerRole};
|
|
use kanidmd_core::create_server_core;
|
|
use kanidmd_testkit::{is_free_port, PORT_ALLOC};
|
|
use tokio::task;
|
|
use tracing::log::{debug, trace};
|
|
|
|
use kanidm_hsm_crypto::{soft::SoftTpm, AuthValue, BoxedDynTpm, Tpm};
|
|
|
|
const ADMIN_TEST_USER: &str = "admin";
|
|
const ADMIN_TEST_PASSWORD: &str = "integration test admin password";
|
|
const IDM_ADMIN_TEST_USER: &str = "idm_admin";
|
|
const IDM_ADMIN_TEST_PASSWORD: &str = "integration test idm_admin password";
|
|
const TESTACCOUNT1_PASSWORD_A: &str = "password a for account1 test";
|
|
const TESTACCOUNT1_PASSWORD_B: &str = "password b for account1 test";
|
|
const TESTACCOUNT1_PASSWORD_INC: &str = "never going to work";
|
|
const ACCOUNT_EXPIRE: &str = "1970-01-01T00:00:00+00:00";
|
|
|
|
type Fixture = Box<dyn FnOnce(KanidmClient) -> Pin<Box<dyn Future<Output = ()>>>>;
|
|
|
|
fn fixture<T>(f: fn(KanidmClient) -> T) -> Fixture
|
|
where
|
|
T: Future<Output = ()> + 'static,
|
|
{
|
|
Box::new(move |n| Box::pin(f(n)))
|
|
}
|
|
|
|
async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
|
|
sketching::test_init();
|
|
|
|
let mut counter = 0;
|
|
let port = loop {
|
|
let possible_port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
|
if is_free_port(possible_port) {
|
|
break possible_port;
|
|
}
|
|
counter += 1;
|
|
#[allow(clippy::assertions_on_constants)]
|
|
if counter >= 5 {
|
|
eprintln!("Unable to allocate port!");
|
|
debug_assert!(false);
|
|
}
|
|
};
|
|
|
|
let int_config = Box::new(IntegrationTestConfig {
|
|
admin_user: ADMIN_TEST_USER.to_string(),
|
|
admin_password: ADMIN_TEST_PASSWORD.to_string(),
|
|
idm_admin_user: IDM_ADMIN_TEST_USER.to_string(),
|
|
idm_admin_password: IDM_ADMIN_TEST_PASSWORD.to_string(),
|
|
});
|
|
|
|
// Setup the config ...
|
|
let mut config = Configuration::new();
|
|
config.address = format!("127.0.0.1:{}", port);
|
|
config.integration_test_config = Some(int_config);
|
|
config.role = ServerRole::WriteReplicaNoUI;
|
|
config.threads = 1;
|
|
|
|
create_server_core(config, false)
|
|
.await
|
|
.expect("failed to start server core");
|
|
// We have to yield now to guarantee that the elements are setup.
|
|
task::yield_now().await;
|
|
|
|
// Setup the client, and the address we selected.
|
|
let addr = format!("http://127.0.0.1:{}", port);
|
|
|
|
// Run fixtures
|
|
let adminclient = KanidmClientBuilder::new()
|
|
.address(addr.clone())
|
|
.no_proxy()
|
|
.build()
|
|
.expect("Failed to build sync client");
|
|
|
|
fix_fn(adminclient).await;
|
|
|
|
let client = KanidmClientBuilder::new()
|
|
.address(addr.clone())
|
|
.no_proxy()
|
|
.build()
|
|
.expect("Failed to build async admin client");
|
|
|
|
let rsclient = KanidmClientBuilder::new()
|
|
.address(addr)
|
|
.no_proxy()
|
|
.build()
|
|
.expect("Failed to build client");
|
|
|
|
let db = Db::new(
|
|
"", // The sqlite db path, this is in memory.
|
|
)
|
|
.expect("Failed to setup DB");
|
|
|
|
let mut dbtxn = db.write().await;
|
|
dbtxn.migrate().expect("Unable to migrate cache db");
|
|
|
|
let mut hsm = BoxedDynTpm::new(SoftTpm::new());
|
|
|
|
let auth_value = AuthValue::ephemeral().unwrap();
|
|
|
|
let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap();
|
|
let machine_key = hsm
|
|
.machine_key_load(&auth_value, &loadable_machine_key)
|
|
.unwrap();
|
|
|
|
let system_provider = SystemProvider::new().unwrap();
|
|
|
|
let idprovider = KanidmProvider::new(
|
|
rsclient,
|
|
&KanidmConfig {
|
|
conn_timeout: 1,
|
|
request_timeout: 1,
|
|
pam_allowed_login_groups: vec!["allowed_group".to_string()],
|
|
map_group: vec![GroupMap {
|
|
local: "extensible_group".to_string(),
|
|
with: "testgroup1".to_string(),
|
|
}],
|
|
},
|
|
SystemTime::now(),
|
|
&mut (&mut dbtxn).into(),
|
|
&mut hsm,
|
|
&machine_key,
|
|
)
|
|
.unwrap();
|
|
|
|
drop(machine_key);
|
|
|
|
dbtxn.commit().expect("Unable to commit dbtxn");
|
|
|
|
let cachelayer = Resolver::new(
|
|
db,
|
|
Arc::new(system_provider),
|
|
vec![Arc::new(idprovider)],
|
|
hsm,
|
|
300,
|
|
DEFAULT_SHELL.to_string(),
|
|
DEFAULT_HOME_PREFIX.into(),
|
|
DEFAULT_HOME_ATTR,
|
|
DEFAULT_HOME_ALIAS,
|
|
DEFAULT_UID_ATTR_MAP,
|
|
DEFAULT_GID_ATTR_MAP,
|
|
)
|
|
.await
|
|
.expect("Failed to build cache layer.");
|
|
|
|
// test_fn(cachelayer, client);
|
|
(cachelayer, client)
|
|
// We DO NOT need teardown, as sqlite is in mem
|
|
// let the tables hit the floor
|
|
}
|
|
|
|
/// This is the test fixture. It sets up the following:
|
|
/// - adds admin to idm_admins
|
|
/// - creates a test account (testaccount1)
|
|
/// - extends the test account with posix attrs
|
|
/// - adds a ssh public key to the test account
|
|
/// - sets a posix password for the test account
|
|
/// - creates a test group (testgroup1) and adds the test account to the test group
|
|
/// - extends testgroup1 with posix attrs
|
|
/// - creates two more groups with unix perms (allowed_group, masked_group)
|
|
async fn test_fixture(rsclient: KanidmClient) {
|
|
let res = rsclient
|
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
.await;
|
|
debug!("auth_simple_password res: {:?}", res);
|
|
trace!("{:?}", &res);
|
|
assert!(res.is_ok());
|
|
// Create a new account
|
|
rsclient
|
|
.idm_person_account_create("testaccount1", "Posix Demo Account")
|
|
.await
|
|
.unwrap();
|
|
|
|
// Extend the account with posix attrs.
|
|
rsclient
|
|
.idm_person_account_unix_extend("testaccount1", Some(20000), None)
|
|
.await
|
|
.unwrap();
|
|
// Assign an ssh public key.
|
|
rsclient
|
|
.idm_person_account_post_ssh_pubkey("testaccount1", "tk",
|
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst")
|
|
.await
|
|
.unwrap();
|
|
// Set a posix password
|
|
rsclient
|
|
.idm_person_account_unix_cred_put("testaccount1", TESTACCOUNT1_PASSWORD_A)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Setup a group
|
|
rsclient.idm_group_create("testgroup1", None).await.unwrap();
|
|
rsclient
|
|
.idm_group_add_members("testgroup1", &["testaccount1"])
|
|
.await
|
|
.unwrap();
|
|
rsclient
|
|
.idm_group_unix_extend("testgroup1", Some(20001))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Setup the allowed group
|
|
rsclient
|
|
.idm_group_create("allowed_group", None)
|
|
.await
|
|
.unwrap();
|
|
rsclient
|
|
.idm_group_unix_extend("allowed_group", Some(20002))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Setup a group that is masked by nxset, but allowed in overrides
|
|
rsclient
|
|
.idm_group_create("masked_group", None)
|
|
.await
|
|
.unwrap();
|
|
rsclient
|
|
.idm_group_unix_extend("masked_group", Some(20003))
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_sshkey() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
// Force offline. Show we have no keys.
|
|
cachelayer.mark_offline().await;
|
|
|
|
let sk = cachelayer
|
|
.get_sshkeys("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache.");
|
|
assert!(sk.is_empty());
|
|
|
|
// Bring ourselves online.
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
let sk = cachelayer
|
|
.get_sshkeys("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache.");
|
|
assert_eq!(sk.len(), 1);
|
|
|
|
// Go offline, and get from cache.
|
|
cachelayer.mark_offline().await;
|
|
let sk = cachelayer
|
|
.get_sshkeys("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache.");
|
|
assert_eq!(sk.len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_account() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
// Force offline. Show we have no account
|
|
cachelayer.mark_offline().await;
|
|
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_none());
|
|
|
|
// go online
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// get the account
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
// #392: Check that a `shell=None` is set to `default_shell`.
|
|
assert_eq!(ut.unwrap().shell, *DEFAULT_SHELL);
|
|
|
|
// go offline
|
|
cachelayer.mark_offline().await;
|
|
|
|
// can still get account
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
// Finally, check we have "all accounts" in the list.
|
|
let us = cachelayer
|
|
.get_nssaccounts()
|
|
.await
|
|
.expect("failed to list all accounts");
|
|
assert_eq!(us.len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_group() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
// Force offline. Show we have no groups.
|
|
cachelayer.mark_offline().await;
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_none());
|
|
|
|
// go online. Get the group
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_some());
|
|
|
|
// go offline. still works
|
|
cachelayer.mark_offline().await;
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_some());
|
|
// And check we have no members in the group. Members are an artifact of
|
|
// user lookups!
|
|
assert!(gt.unwrap().members.is_empty());
|
|
|
|
// clear cache, go online
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// get an account with the group
|
|
// DO NOT get the group yet.
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
// go offline.
|
|
cachelayer.mark_offline().await;
|
|
|
|
// show we have the group despite no direct calls
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_some());
|
|
// And check we have members in the group, since we came from a userlook up
|
|
assert_eq!(gt.unwrap().members.len(), 1);
|
|
|
|
// Finally, check we have "all groups" in the list.
|
|
let gs = cachelayer
|
|
.get_nssgroups()
|
|
.await
|
|
.expect("failed to list all groups");
|
|
assert_eq!(gs.len(), 2);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_group_delete() {
|
|
let (cachelayer, adminclient) = setup_test(fixture(test_fixture)).await;
|
|
// get the group
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_some());
|
|
|
|
// delete it.
|
|
adminclient
|
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
.await
|
|
.expect("failed to auth as admin");
|
|
adminclient
|
|
.idm_group_delete("testgroup1")
|
|
.await
|
|
.expect("failed to delete");
|
|
|
|
// invalidate cache
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
|
|
// "get it"
|
|
// should be empty.
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_account_delete() {
|
|
let (cachelayer, adminclient) = setup_test(fixture(test_fixture)).await;
|
|
// get the account
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
// delete it.
|
|
adminclient
|
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
.await
|
|
.expect("failed to auth as admin");
|
|
adminclient
|
|
.idm_person_account_delete("testaccount1")
|
|
.await
|
|
.expect("failed to delete");
|
|
|
|
// invalidate cache
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
|
|
// "get it"
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
// should be empty.
|
|
assert!(ut.is_none());
|
|
|
|
// The group should be removed too.
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_account_password() {
|
|
let current_time = OffsetDateTime::now_utc();
|
|
let (cachelayer, adminclient) = setup_test(fixture(test_fixture)).await;
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
// Test authentication failure.
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_INC)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(false));
|
|
|
|
// We have to wait due to softlocking.
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
// Test authentication success.
|
|
let a2 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_A)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a2, Some(true));
|
|
|
|
// change pw
|
|
adminclient
|
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
.await
|
|
.expect("failed to auth as admin");
|
|
adminclient
|
|
.idm_person_account_unix_cred_put("testaccount1", TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("Failed to change password");
|
|
|
|
// test auth (old pw) fail
|
|
let a3 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_A)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a3, Some(false));
|
|
|
|
// We have to wait due to softlocking.
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
// test auth (new pw) success
|
|
let a4 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a4, Some(true));
|
|
|
|
// Go offline.
|
|
cachelayer.mark_offline().await;
|
|
|
|
// Test auth success
|
|
let a5 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a5, Some(true));
|
|
|
|
// No softlock during offline.
|
|
|
|
// Test auth failure.
|
|
let a6 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_INC)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a6, Some(false));
|
|
|
|
// clear cache
|
|
cachelayer
|
|
.clear_cache()
|
|
.await
|
|
.expect("failed to clear cache");
|
|
|
|
// test auth good (fail)
|
|
let a7 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert!(a7.is_none());
|
|
|
|
// go online
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// test auth success
|
|
let a8 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a8, Some(true));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_account_pam_allowed() {
|
|
let (cachelayer, adminclient) = setup_test(fixture(test_fixture)).await;
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
|
|
// Should fail
|
|
let a1 = cachelayer
|
|
.pam_account_allowed("testaccount1")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(false));
|
|
|
|
adminclient
|
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
.await
|
|
.expect("failed to auth as admin");
|
|
adminclient
|
|
.idm_group_add_members("allowed_group", &["testaccount1"])
|
|
.await
|
|
.unwrap();
|
|
|
|
// Invalidate cache to force a refresh
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
|
|
// Should pass
|
|
let a2 = cachelayer
|
|
.pam_account_allowed("testaccount1")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a2, Some(true));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_account_pam_nonexist() {
|
|
let current_time = OffsetDateTime::now_utc();
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
|
|
let a1 = cachelayer
|
|
.pam_account_allowed("NO_SUCH_ACCOUNT")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert!(a1.is_none());
|
|
|
|
let a2 = cachelayer
|
|
.pam_account_authenticate("NO_SUCH_ACCOUNT", current_time, TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert!(a2.is_none());
|
|
|
|
cachelayer.mark_offline().await;
|
|
|
|
let a1 = cachelayer
|
|
.pam_account_allowed("NO_SUCH_ACCOUNT")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert!(a1.is_none());
|
|
|
|
let a2 = cachelayer
|
|
.pam_account_authenticate("NO_SUCH_ACCOUNT", current_time, TESTACCOUNT1_PASSWORD_B)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert!(a2.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_account_expiry() {
|
|
let current_time = OffsetDateTime::now_utc();
|
|
let (cachelayer, adminclient) = setup_test(fixture(test_fixture)).await;
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// We need one good auth first to prime the cache with a hash.
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_A)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(true));
|
|
// Invalidate to make sure we go online next checks.
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
|
|
// expire the account
|
|
adminclient
|
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
.await
|
|
.expect("failed to auth as admin");
|
|
adminclient
|
|
.idm_person_account_set_attr("testaccount1", ATTR_ACCOUNT_EXPIRE, &[ACCOUNT_EXPIRE])
|
|
.await
|
|
.unwrap();
|
|
// auth will fail
|
|
let a2 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_A)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a2, Some(false));
|
|
|
|
// ssh keys should be empty
|
|
let sk = cachelayer
|
|
.get_sshkeys("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache.");
|
|
assert!(sk.is_empty());
|
|
|
|
// Pam account allowed should be denied.
|
|
let a3 = cachelayer
|
|
.pam_account_allowed("testaccount1")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a3, Some(false));
|
|
|
|
// go offline
|
|
cachelayer.mark_offline().await;
|
|
|
|
// Now, check again. Since this uses the cached pw and we are offline, this
|
|
// will now succeed.
|
|
let a4 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, TESTACCOUNT1_PASSWORD_A)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a4, Some(true));
|
|
|
|
// ssh keys should be empty
|
|
let sk = cachelayer
|
|
.get_sshkeys("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache.");
|
|
assert!(sk.is_empty());
|
|
|
|
// Pam account allowed should be denied.
|
|
let a5 = cachelayer
|
|
.pam_account_allowed("testaccount1")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a5, Some(false));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_nxcache() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
// Is it in the nxcache?
|
|
|
|
assert!(cachelayer
|
|
.check_nxcache(&Id::Name("oracle".to_string()))
|
|
.await
|
|
.is_none());
|
|
assert!(cachelayer.check_nxcache(&Id::Gid(2000)).await.is_none());
|
|
assert!(cachelayer
|
|
.check_nxcache(&Id::Name("oracle_group".to_string()))
|
|
.await
|
|
.is_none());
|
|
assert!(cachelayer.check_nxcache(&Id::Gid(3000)).await.is_none());
|
|
|
|
// Look for the acc id + nss id
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("oracle")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_none());
|
|
let ut = cachelayer
|
|
.get_nssaccount_gid(2000)
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_none());
|
|
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("oracle_group")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_none());
|
|
let gt = cachelayer
|
|
.get_nssgroup_gid(3000)
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_none());
|
|
|
|
// Should all now be nxed
|
|
assert!(
|
|
cachelayer
|
|
.check_nxcache(&Id::Name("oracle".to_string()))
|
|
.await
|
|
.is_some(),
|
|
"'oracle' Wasn't in the nxcache!"
|
|
);
|
|
assert!(cachelayer.check_nxcache(&Id::Gid(2000)).await.is_some());
|
|
assert!(cachelayer
|
|
.check_nxcache(&Id::Name("oracle_group".to_string()))
|
|
.await
|
|
.is_some());
|
|
assert!(cachelayer.check_nxcache(&Id::Gid(3000)).await.is_some());
|
|
|
|
// invalidate cache
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
|
|
// Both should NOT be in nxcache now.
|
|
assert!(cachelayer
|
|
.check_nxcache(&Id::Name("oracle".to_string()))
|
|
.await
|
|
.is_none());
|
|
assert!(cachelayer.check_nxcache(&Id::Gid(2000)).await.is_none());
|
|
assert!(cachelayer
|
|
.check_nxcache(&Id::Name("oracle_group".to_string()))
|
|
.await
|
|
.is_none());
|
|
assert!(cachelayer.check_nxcache(&Id::Gid(3000)).await.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_nxset_account() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
|
|
// Important! This is what sets up that testaccount1 won't be resolved
|
|
// because it's in the "local" user set.
|
|
cachelayer
|
|
.reload_system_identities(
|
|
vec![EtcUser {
|
|
name: "testaccount1".to_string(),
|
|
uid: 30000,
|
|
gid: 30000,
|
|
password: Default::default(),
|
|
gecos: Default::default(),
|
|
homedir: Default::default(),
|
|
shell: Default::default(),
|
|
}],
|
|
None,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
// go online
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// get the account
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let ut = ut.unwrap();
|
|
// Assert the user is the system version.
|
|
assert_eq!(ut.uid, 30000);
|
|
|
|
// go offline
|
|
cachelayer.mark_offline().await;
|
|
|
|
// still not present, was not cached.
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let ut = ut.unwrap();
|
|
// Assert the user is the system version.
|
|
assert_eq!(ut.uid, 30000);
|
|
|
|
// Finally, check it's the system version in all accounts.
|
|
let us = cachelayer
|
|
.get_nssaccounts()
|
|
.await
|
|
.expect("failed to list all accounts");
|
|
|
|
let us: Vec<_> = us
|
|
.into_iter()
|
|
.filter(|nss_user| nss_user.name == "testaccount1")
|
|
.collect();
|
|
|
|
assert_eq!(us.len(), 1);
|
|
assert_eq!(us[0].gid, 30000);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_nxset_group() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
|
|
// Important! This is what sets up that testgroup1 won't be resolved
|
|
// because it's in the "local" group set.
|
|
cachelayer
|
|
.reload_system_identities(
|
|
vec![],
|
|
None,
|
|
vec![EtcGroup {
|
|
name: "testgroup1".to_string(),
|
|
// Important! We set the GID to differ from what kanidm stores so we can
|
|
// tell we got the system version.
|
|
gid: 30001,
|
|
password: Default::default(),
|
|
members: Default::default(),
|
|
}],
|
|
)
|
|
.await;
|
|
|
|
// go online. Get the group
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
// We get the group, it's the system version. Check the gid.
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 30001);
|
|
|
|
// go offline. still works
|
|
cachelayer.mark_offline().await;
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 30001);
|
|
|
|
// clear cache, go online
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// get a kanidm account with the kanidm equivalent group
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
// go offline.
|
|
cachelayer.mark_offline().await;
|
|
|
|
// show that the group we have is still the system version, and lacks our
|
|
// member.
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 30001);
|
|
assert!(gt.members.is_empty());
|
|
|
|
// Finally, check we only have the system group version in the list.
|
|
let gs = cachelayer
|
|
.get_nssgroups()
|
|
.await
|
|
.expect("failed to list all groups");
|
|
|
|
let gs: Vec<_> = gs
|
|
.into_iter()
|
|
.filter(|nss_group| nss_group.name == "testgroup1")
|
|
.collect();
|
|
|
|
debug!("{:?}", gs);
|
|
assert_eq!(gs.len(), 1);
|
|
assert_eq!(gs[0].gid, 30001);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cache_authenticate_system_account() {
|
|
const SECURE_PASSWORD: &str = "a";
|
|
|
|
let current_time = OffsetDateTime::UNIX_EPOCH + time::Duration::days(365);
|
|
let expire_time = OffsetDateTime::UNIX_EPOCH + time::Duration::days(380);
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
|
|
// Important! This is what sets up that testaccount1 won't be resolved
|
|
// because it's in the "local" user set.
|
|
cachelayer
|
|
.reload_system_identities(
|
|
vec![
|
|
EtcUser {
|
|
name: "testaccount1".to_string(),
|
|
uid: 30000,
|
|
gid: 30000,
|
|
password: Default::default(),
|
|
gecos: Default::default(),
|
|
homedir: Default::default(),
|
|
shell: Default::default(),
|
|
},
|
|
EtcUser {
|
|
name: "testaccount2".to_string(),
|
|
uid: 30001,
|
|
gid: 30001,
|
|
password: Default::default(),
|
|
gecos: Default::default(),
|
|
homedir: Default::default(),
|
|
shell: Default::default(),
|
|
}
|
|
],
|
|
Some(vec![
|
|
EtcShadow {
|
|
name: "testaccount1".to_string(),
|
|
// The very secure password, "a".
|
|
password: CryptPw::Sha512("$6$5.bXZTIXuVv.xI3.$sAubscCJPwnBWwaLt2JR33lo539UyiDku.aH5WVSX0Tct9nGL2ePMEmrqT3POEdBlgNQ12HJBwskewGu2dpF//".to_string()),
|
|
epoch_change_days: None,
|
|
days_min_password_age: 0,
|
|
days_max_password_age: Some(1),
|
|
days_warning_period: 1,
|
|
days_inactivity_period: None,
|
|
epoch_expire_date: Some(380),
|
|
flag_reserved: None
|
|
},
|
|
EtcShadow {
|
|
name: "testaccount2".to_string(),
|
|
// The very secure password, "a".
|
|
password: CryptPw::Sha512("$6$5.bXZTIXuVv.xI3.$sAubscCJPwnBWwaLt2JR33lo539UyiDku.aH5WVSX0Tct9nGL2ePMEmrqT3POEdBlgNQ12HJBwskewGu2dpF//".to_string()),
|
|
epoch_change_days: Some(364),
|
|
days_min_password_age: 0,
|
|
days_max_password_age: Some(2),
|
|
days_warning_period: 1,
|
|
days_inactivity_period: None,
|
|
epoch_expire_date: Some(380),
|
|
flag_reserved: None
|
|
},
|
|
]),
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
// get the accounts to assert they exist,
|
|
let _ = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
let _ = cachelayer
|
|
.get_nssaccount_name("testaccount2")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
// Non exist name
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount69", current_time, SECURE_PASSWORD)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, None);
|
|
|
|
// Check wrong pw.
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, "wrong password")
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(false));
|
|
|
|
// Check correct pw (both accounts)
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount1", current_time, SECURE_PASSWORD)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(true));
|
|
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount2", current_time, SECURE_PASSWORD)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(true));
|
|
|
|
// Check expired time (both accounts)
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount1", expire_time, SECURE_PASSWORD)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(false));
|
|
|
|
let a1 = cachelayer
|
|
.pam_account_authenticate("testaccount2", expire_time, SECURE_PASSWORD)
|
|
.await
|
|
.expect("failed to authenticate");
|
|
assert_eq!(a1, Some(false));
|
|
|
|
// due to how posix auth works, session and authorisation are simpler, and should
|
|
// always just return "true".
|
|
let a1 = cachelayer
|
|
.pam_account_allowed("testaccount1")
|
|
.await
|
|
.expect("failed to authorise");
|
|
assert_eq!(a1, Some(true));
|
|
|
|
let a1 = cachelayer
|
|
.pam_account_allowed("testaccount2")
|
|
.await
|
|
.expect("failed to authorise");
|
|
assert_eq!(a1, Some(true));
|
|
|
|
// Should we make home dirs?
|
|
let a1 = cachelayer
|
|
.pam_account_beginsession("testaccount1")
|
|
.await
|
|
.expect("failed to begin session");
|
|
assert_eq!(a1, None);
|
|
|
|
let a1 = cachelayer
|
|
.pam_account_beginsession("testaccount2")
|
|
.await
|
|
.expect("failed to begin session");
|
|
assert_eq!(a1, None);
|
|
}
|
|
|
|
/// Issue 1830. If cache items expire where we have an account and a group, and we
|
|
/// refresh the group *first*, the group appears to drop it's members. This is because
|
|
/// sqlite "INSERT OR REPLACE INTO" triggers a delete cascade of the foreign key elements
|
|
/// which then makes the group appear empty.
|
|
///
|
|
/// We can reproduce this by retrieving an account + group, wait for expiry, then retrieve
|
|
/// only the group.
|
|
#[tokio::test]
|
|
async fn test_cache_group_fk_deferred() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// Get the account then the group.
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_some());
|
|
assert_eq!(gt.unwrap().members.len(), 1);
|
|
|
|
// Invalidate all items.
|
|
cachelayer.mark_offline().await;
|
|
assert!(cachelayer.invalidate().await.is_ok());
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
// Get the *group*. It *should* still have it's members.
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_some());
|
|
// And check we have members in the group, since we came from a userlook up
|
|
assert_eq!(gt.unwrap().members.len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
/// Test group extension. Groups extension is not the same as "overriding". Extension
|
|
/// only allows the *members* of a remote group to supplement the members of the local
|
|
/// group. This prevents a remote group changing the gidnumber of the local group and
|
|
/// causing breakages.
|
|
async fn test_cache_extend_group_members() {
|
|
let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await;
|
|
|
|
cachelayer
|
|
.reload_system_identities(
|
|
vec![EtcUser {
|
|
name: "local_account".to_string(),
|
|
uid: 30000,
|
|
gid: 30000,
|
|
password: Default::default(),
|
|
gecos: Default::default(),
|
|
homedir: Default::default(),
|
|
shell: Default::default(),
|
|
}],
|
|
None,
|
|
vec![EtcGroup {
|
|
// This group is configured to allow extension from
|
|
// the group "testgroup1"
|
|
name: "extensible_group".to_string(),
|
|
gid: 30001,
|
|
password: Default::default(),
|
|
// We have the local account as a member, it should NOT be stomped.
|
|
members: vec!["local_account".to_string()],
|
|
}],
|
|
)
|
|
.await;
|
|
|
|
// Force offline. Show we have no groups.
|
|
cachelayer.mark_offline().await;
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(gt.is_none());
|
|
|
|
// While offline, extensible_group has only local_account as a member.
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("extensible_group")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 30001);
|
|
assert_eq!(gt.members.as_slice(), &["local_account".to_string()]);
|
|
|
|
// Go online. Group now exists, extensible_group has group members.
|
|
// Need to resolve test-account first so that the membership is linked.
|
|
cachelayer.mark_next_check_now(SystemTime::now()).await;
|
|
assert!(cachelayer.test_connection().await);
|
|
|
|
let ut = cachelayer
|
|
.get_nssaccount_name("testaccount1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
assert!(ut.is_some());
|
|
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 20001);
|
|
assert_eq!(
|
|
gt.members.as_slice(),
|
|
&["testaccount1@idm.example.com".to_string()]
|
|
);
|
|
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("extensible_group")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
// Even though it's extended, still needs to be the local uid/gid
|
|
assert_eq!(gt.gid, 30001);
|
|
assert_eq!(
|
|
gt.members.as_slice(),
|
|
&[
|
|
"local_account".to_string(),
|
|
"testaccount1@idm.example.com".to_string()
|
|
]
|
|
);
|
|
|
|
let groups = cachelayer
|
|
.get_nssgroups()
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
assert!(groups.iter().any(|group| {
|
|
group.name == "extensible_group"
|
|
&& group.members.as_slice()
|
|
== &[
|
|
"local_account".to_string(),
|
|
"testaccount1@idm.example.com".to_string(),
|
|
]
|
|
}));
|
|
|
|
// Go offline. Group cached, extensible_group has members.
|
|
cachelayer.mark_offline().await;
|
|
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("testgroup1")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 20001);
|
|
assert_eq!(
|
|
gt.members.as_slice(),
|
|
&["testaccount1@idm.example.com".to_string()]
|
|
);
|
|
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("extensible_group")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
// Even though it's extended, still needs to be the local uid/gid
|
|
assert_eq!(gt.gid, 30001);
|
|
assert_eq!(
|
|
gt.members.as_slice(),
|
|
&[
|
|
"local_account".to_string(),
|
|
"testaccount1@idm.example.com".to_string()
|
|
]
|
|
);
|
|
|
|
// clear cache
|
|
cachelayer
|
|
.clear_cache()
|
|
.await
|
|
.expect("failed to clear cache");
|
|
|
|
// No longer has testaccount.
|
|
let gt = cachelayer
|
|
.get_nssgroup_name("extensible_group")
|
|
.await
|
|
.expect("Failed to get from cache");
|
|
|
|
let gt = gt.unwrap();
|
|
assert_eq!(gt.gid, 30001);
|
|
assert_eq!(gt.members.as_slice(), &["local_account".to_string()]);
|
|
}
|