CLI integration test beginnings (#2261)

* more integration test things, using assert_cmd to test the CLI end-to-end
* packagez
* making clippy happy
* making deno happy
This commit is contained in:
James Hodgkinson 2023-10-30 16:10:54 +10:00 committed by GitHub
parent 8cd999d342
commit 3bfc347c53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 537 additions and 202 deletions

View file

@ -64,6 +64,7 @@ jobs:
RUSTC_WRAPPER: sccache
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
MALLOC_CONF: "thp:always,metadata_thp:always"
steps:
- uses: actions/checkout@v4
- name: Install Rust

100
Cargo.lock generated
View file

@ -187,6 +187,21 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "assert_cmd"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "async-compression"
version = "0.4.3"
@ -891,7 +906,7 @@ dependencies = [
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"itertools 0.10.5",
"num-traits",
"once_cell",
"oorandom",
@ -912,7 +927,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
"itertools 0.10.5",
]
[[package]]
@ -1265,6 +1280,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "digest"
version = "0.8.1"
@ -1316,6 +1337,12 @@ dependencies = [
"syn 2.0.38",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dunce"
version = "1.0.4"
@ -1457,6 +1484,18 @@ dependencies = [
"libc",
]
[[package]]
name = "escargot"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "768064bd3a0e2bedcba91dc87ace90beea91acc41b6a01a3ca8e9aa8827461bf"
dependencies = [
"log",
"once_cell",
"serde",
"serde_json",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -2819,6 +2858,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -3179,7 +3227,9 @@ dependencies = [
name = "kanidmd_testkit"
version = "1.1.0-rc.14-dev"
dependencies = [
"assert_cmd",
"compact_jwt",
"escargot",
"fantoccini",
"futures",
"http",
@ -3199,6 +3249,7 @@ dependencies = [
"serde",
"serde_json",
"sketching",
"tempfile",
"testkit-macros",
"time",
"tokio",
@ -4295,6 +4346,34 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "predicates"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
dependencies = [
"anstyle",
"difflib",
"itertools 0.11.0",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
[[package]]
name = "predicates-tree"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "prettyplease"
version = "0.1.25"
@ -5342,6 +5421,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "termtree"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "testkit-macros"
version = "0.1.0"
@ -5902,6 +5987,15 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.4.0"
@ -6598,7 +6692,7 @@ checksum = "103fa851fff70ea29af380e87c25c48ff7faac5c530c70bd0e65366d4e0c94e4"
dependencies = [
"derive_builder",
"fancy-regex",
"itertools",
"itertools 0.10.5",
"js-sys",
"lazy_static",
"quick-error",

View file

@ -183,6 +183,8 @@ cp -a /etc/pam.d /root/pam.d.backup
Documentation examples for the following Linux distributions are available:
* [Fedora](pam_and_nsswitch/fedora.md)
* [SUSE / OpenSUSE](pam_and_nsswitch/suse.md)
* Debian / Ubuntu - when one generates packages [from the repository tools](https://github.com/kanidm/kanidm/tree/master/platform/debian), configuration is modified on install.
- [SUSE / OpenSUSE](pam_and_nsswitch/suse.md)
- [Fedora](pam_and_nsswitch/fedora.md)
- Debian / Ubuntu - when one generates packages
[from the repository tools](https://github.com/kanidm/kanidm/tree/master/platform/debian),
configuration is modified on install.

View file

@ -25,7 +25,9 @@ use std::path::Path;
use std::time::Duration;
use kanidm_proto::constants::uri::V1_AUTH_VALID;
use kanidm_proto::constants::{APPLICATION_JSON, ATTR_NAME, KOPID, KSESSIONID, KVERSION};
use kanidm_proto::constants::{
APPLICATION_JSON, ATTR_NAME, CLIENT_TOKEN_CACHE, KOPID, KSESSIONID, KVERSION,
};
use kanidm_proto::v1::*;
use reqwest::header::CONTENT_TYPE;
use reqwest::Response;
@ -85,6 +87,8 @@ pub struct KanidmClientBuilder {
ca: Option<reqwest::Certificate>,
connect_timeout: Option<u64>,
use_system_proxies: bool,
/// Where to store auth tokens, only use in testing!
token_cache_path: Option<String>,
}
impl Display for KanidmClientBuilder {
@ -103,7 +107,14 @@ impl Display for KanidmClientBuilder {
Some(value) => writeln!(f, "connect_timeout: {}", value)?,
None => writeln!(f, "connect_timeout: unset")?,
}
writeln!(f, "use_system_proxies: {}", self.use_system_proxies)
writeln!(f, "use_system_proxies: {}", self.use_system_proxies)?;
writeln!(
f,
"token_cache_path: {}",
self.token_cache_path
.clone()
.unwrap_or(CLIENT_TOKEN_CACHE.to_string())
)
}
}
@ -120,6 +131,7 @@ fn test_kanidmclientbuilder_display() {
ca: None,
connect_timeout: Some(420),
use_system_proxies: true,
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
};
println!("foo {}", testclient);
assert!(testclient.to_string().contains("verify_ca: true"));
@ -141,6 +153,8 @@ pub struct KanidmClient {
pub(crate) bearer_token: RwLock<Option<String>>,
pub(crate) auth_session_id: RwLock<Option<String>>,
pub(crate) check_version: Mutex<bool>,
/// Where to store the tokens when you auth, only modify in testing.
token_cache_path: String,
}
#[cfg(target_family = "unix")]
@ -163,6 +177,7 @@ impl KanidmClientBuilder {
ca: None,
connect_timeout: None,
use_system_proxies: true,
token_cache_path: None,
}
}
@ -218,6 +233,7 @@ impl KanidmClientBuilder {
ca,
connect_timeout,
use_system_proxies,
token_cache_path,
} = self;
// Process and apply all our options if they exist.
let address = match kcc.uri {
@ -241,6 +257,7 @@ impl KanidmClientBuilder {
ca,
connect_timeout,
use_system_proxies,
token_cache_path,
})
}
@ -313,57 +330,37 @@ impl KanidmClientBuilder {
pub fn address(self, address: String) -> Self {
KanidmClientBuilder {
address: Some(address),
verify_ca: self.verify_ca,
verify_hostnames: self.verify_hostnames,
ca: self.ca,
connect_timeout: self.connect_timeout,
use_system_proxies: self.use_system_proxies,
..self
}
}
pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostnames: bool) -> Self {
KanidmClientBuilder {
address: self.address,
verify_ca: self.verify_ca,
// We have to flip the bool state here due to english language.
verify_hostnames: !accept_invalid_hostnames,
ca: self.ca,
connect_timeout: self.connect_timeout,
use_system_proxies: self.use_system_proxies,
..self
}
}
pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> Self {
KanidmClientBuilder {
address: self.address,
// We have to flip the bool state here due to english language.
verify_ca: !accept_invalid_certs,
verify_hostnames: self.verify_hostnames,
ca: self.ca,
connect_timeout: self.connect_timeout,
use_system_proxies: self.use_system_proxies,
..self
}
}
pub fn connect_timeout(self, secs: u64) -> Self {
KanidmClientBuilder {
address: self.address,
verify_ca: self.verify_ca,
verify_hostnames: self.verify_hostnames,
ca: self.ca,
connect_timeout: Some(secs),
use_system_proxies: self.use_system_proxies,
..self
}
}
pub fn no_proxy(self) -> Self {
KanidmClientBuilder {
address: self.address,
verify_ca: self.verify_ca,
verify_hostnames: self.verify_hostnames,
ca: self.ca,
connect_timeout: self.connect_timeout,
use_system_proxies: false,
..self
}
}
@ -376,12 +373,8 @@ impl KanidmClientBuilder {
})?;
Ok(KanidmClientBuilder {
address: self.address,
verify_ca: self.verify_ca,
verify_hostnames: self.verify_hostnames,
ca: Some(ca),
connect_timeout: self.connect_timeout,
use_system_proxies: self.use_system_proxies,
..self
})
}
@ -396,7 +389,6 @@ impl KanidmClientBuilder {
"verify_hostnames set to false in client configuration - this may allow network interception of passwords!"
);
}
if !address.starts_with("https://") {
warn!("Address does not start with 'https://' - this may allow network interception of passwords!");
}
@ -463,6 +455,11 @@ impl KanidmClientBuilder {
let origin =
Url::parse(&uri.origin().ascii_serialization()).expect("failed to parse origin");
let token_cache_path = match self.token_cache_path.clone() {
Some(val) => val.to_string(),
None => CLIENT_TOKEN_CACHE.to_string(),
};
Ok(KanidmClient {
client,
addr: address,
@ -471,6 +468,7 @@ impl KanidmClientBuilder {
origin,
auth_session_id: RwLock::new(None),
check_version: Mutex::new(true),
token_cache_path,
})
}
}
@ -561,6 +559,10 @@ impl KanidmClient {
Ok(())
}
pub fn get_token_cache_path(&self) -> String {
self.token_cache_path.clone()
}
/// Check that we're getting the right version back from the server.
async fn expect_version(&self, response: &reqwest::Response) {
let mut guard = self.check_version.lock().await;

View file

@ -2,22 +2,33 @@
#![warn(unused_extern_crates)]
#![allow(non_snake_case)]
use num_enum::{IntoPrimitive, TryFromPrimitive};
use tracing_forest::printer::TestCapturePrinter;
use tracing_forest::tag::NoTag;
use tracing_forest::util::*;
use tracing_forest::Tag;
use tracing_subscriber::prelude::*;
pub mod macros;
pub use {tracing, tracing_forest, tracing_subscriber};
/// Start up the logging for test mode.
pub fn test_init() {
// tracing_subscriber::fmt::try_init()
let _ = tracing_forest::test_init();
/*
let _ = Registry::default().with(ForestLayer::new(
TestCapturePrinter::new(),
NoTag,
)).try_init();
*/
let filter = EnvFilter::from_default_env()
.add_directive(LevelFilter::TRACE.into())
// escargot builds cargo packages while we integration test and is SUPER noisy.
.add_directive(
"escargot=ERROR"
.parse()
.expect("failed to generate log filter"),
)
// hyper's very noisy in debug mode with connectivity-related things that we only need in extreme cases.
.add_directive("hyper=INFO".parse().expect("failed to generate log filter"));
// start the logging!
let _ = tracing_subscriber::Registry::default()
.with(ForestLayer::new(TestCapturePrinter::new(), NoTag).with_filter(filter))
.try_init();
}
/// This is for tagging events. Currently not wired in.

View file

@ -2,6 +2,9 @@
//!
pub mod uri;
/// The default location for the `kanidm` CLI tool's token cache.
pub const CLIENT_TOKEN_CACHE: &str = "~/.cache/kanidm_tokens";
pub const CONTENT_TYPE_JPG: &str = "image/jpeg";
pub const CONTENT_TYPE_PNG: &str = "image/png";
pub const CONTENT_TYPE_GIF: &str = "image/gif";

View file

@ -5,5 +5,6 @@ This dir has some things for setting up a simple OAuth2 RS so things can get tes
## Quick Setup
1. Run the `setup_dev_environment.sh` script and set a credential for `testuser`.
2. Look for the OAuth2 Secret in the script output and copy it into a file called `client.secret` in this dir.
2. Look for the OAuth2 Secret in the script output and copy it into a file called `client.secret` in
this dir.
3. Run `./run_proxy.sh` to start the proxy, and then go to the URL and do the thing!

View file

@ -60,7 +60,6 @@ struct DbScanListIndex {
commonopts: CommonOpt,
}
#[derive(Debug, Parser)]
struct HealthCheckArgs {
/// Disable TLS verification
@ -200,5 +199,5 @@ enum KanidmdOpt {
/// Print the program version and exit
#[clap(name = "version")]
Version(CommonOpt)
Version(CommonOpt),
}

View file

@ -16,8 +16,39 @@ mod entry;
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_attribute]
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
entry::test(args, item)
}
#[proc_macro]
/// used in testkit to build and run the kanidm binary with the correct environment variables
pub fn cli_kanidm(_input: TokenStream) -> TokenStream {
let code = quote! {
{
// get the manifest path for the kanidm binary
let cli_manifest_file_path =
format!("{}/../../tools/cli/Cargo.toml", env!("CARGO_MANIFEST_DIR"));
let cli_manifest_file = std::path::Path::new(&cli_manifest_file_path)
.canonicalize()
.unwrap();
// make sure we're building/running the current version
let mut kanidm = escargot::CargoBuild::new()
.bin("kanidm")
.current_release()
.current_target()
.manifest_path(&cli_manifest_file)
.run()
.unwrap();
let mut kanidm = kanidm.command();
kanidm.env("KANIDM_URL", &rsclient.get_url().to_string());
kanidm.env("KANIDM_TOKEN_CACHE_PATH", &token_cache_path);
kanidm
}
};
code.into()
}

View file

@ -20,36 +20,44 @@ doctest = false
[features]
default = []
# Enables webdriver tests, you need to be running a webdriver server
webdriver = ["fantoccini"]
webdriver = []
[dependencies]
assert_cmd = "2.0.12"
escargot = "0.5.8"
hyper-tls = { workspace = true }
http = { workspace = true }
kanidm_client = { workspace = true }
kanidm_proto = { workspace = true }
kanidmd_core = { workspace = true }
kanidmd_lib = { workspace = true }
# used for webdriver testing
hyper-tls = { workspace = true }
http = { workspace = true }
# used for webdriver testing
fantoccini = { version = "0.19.3", optional = true }
serde = { workspace = true }
url = { workspace = true, features = ["serde"] }
reqwest = { workspace = true, default-features = false, features = ["cookies"] }
sketching = { workspace = true }
testkit-macros = { workspace = true }
tracing = { workspace = true, features = ["attributes"] }
tokio = { workspace = true, features = ["net", "sync", "io-util", "macros"] }
openssl = { workspace = true }
lazy_static = { workspace = true }
openssl = { workspace = true }
petgraph = { version = "0.6.4", features = ["serde", "serde-1"] }
regex.workspace = true
regex = { workspace = true }
reqwest = { workspace = true, default-features = false, features = ["cookies"] }
serde = { workspace = true }
sketching = { workspace = true }
tempfile = { workspace = true }
testkit-macros = { workspace = true }
tokio = { workspace = true, features = [
"net",
"sync",
"io-util",
"macros",
"rt",
] }
tracing = { workspace = true, features = ["attributes"] }
url = { workspace = true, features = ["serde"] }
[build-dependencies]
kanidm_build_profiles = { workspace = true }
[dev-dependencies]
compact_jwt = { workspace = true }
# used for webdriver testing
fantoccini = { version = "0.19.3" }
serde_json = { workspace = true }
webauthn-authenticator-rs = { workspace = true }
oauth2_ext = { workspace = true, default-features = false }

View file

@ -26,6 +26,8 @@ use tokio::task;
pub const ADMIN_TEST_USER: &str = "admin";
pub const ADMIN_TEST_PASSWORD: &str = "integration test admin password";
pub const IDM_ADMIN_TEST_USER: &str = "idm_admin";
pub const IDM_ADMIN_TEST_PASSWORD: &str = "integration idm admin password";
pub const NOT_ADMIN_TEST_USERNAME: &str = "krab_test_user";
pub const NOT_ADMIN_TEST_PASSWORD: &str = "eicieY7ahchaoCh0eeTa";

View file

@ -1,7 +1,15 @@
//! Integration tests using browser automation
use std::process::Output;
use tempfile::tempdir;
use kanidm_client::KanidmClient;
use kanidmd_testkit::login_put_admin_idm_admins;
use kanidmd_testkit::{
login_put_admin_idm_admins, ADMIN_TEST_PASSWORD, IDM_ADMIN_TEST_PASSWORD, IDM_ADMIN_TEST_USER,
NOT_ADMIN_TEST_USERNAME,
};
use testkit_macros::cli_kanidm;
/// Tries to handle closing the webdriver session if there's an error
#[allow(unused_macros)]
@ -19,7 +27,7 @@ macro_rules! handle_error {
/// Tries to get the webdriver client, trying the default chromedriver port if the default selenium port doesn't work
#[allow(dead_code)]
#[cfg(feature = "webdriver")]
#[cfg(all(feature = "webdriver", any(test, debug_assertions)))]
async fn get_webdriver_client() -> fantoccini::Client {
use fantoccini::wd::Capabilities;
use serde_json::json;
@ -219,3 +227,135 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
.await
.is_err());
}
/// run a test command as the admin user
fn test_cmd_admin(token_cache_path: &str, rsclient: &KanidmClient, cmd: &str) -> Output {
let split_cmd: Vec<&str> = cmd.split_ascii_whitespace().collect();
test_cmd_admin_split(token_cache_path, rsclient, &split_cmd)
}
/// run a test command as the admin user
fn test_cmd_admin_split(token_cache_path: &str, rsclient: &KanidmClient, cmd: &[&str]) -> Output {
println!(
"##################################\nrunning {}\n##################################",
cmd.join(" ")
);
let res = cli_kanidm!()
.env("KANIDM_PASSWORD", ADMIN_TEST_PASSWORD)
.args(cmd)
.output()
.unwrap();
println!("############ result ##################");
println!("status: {:?}", res.status);
println!("stdout: {}", String::from_utf8_lossy(&res.stdout));
println!("stderr: {}", String::from_utf8_lossy(&res.stderr));
println!("######################################");
assert!(res.status.success());
res
}
/// run a test command as the idm_admin user
fn test_cmd_idm_admin(token_cache_path: &str, rsclient: &KanidmClient, cmd: &str) -> Output {
println!("##############################\nrunning {}", cmd);
let res = cli_kanidm!()
.env("KANIDM_PASSWORD", IDM_ADMIN_TEST_PASSWORD)
.args(cmd.split(" "))
.output()
.unwrap();
println!("##############################\n{} result: {:?}", cmd, res);
assert!(res.status.success());
res
}
#[kanidmd_testkit::test]
/// Testing the CLI doing things.
async fn test_integration_with_assert_cmd(rsclient: KanidmClient) {
// setup the admin things
login_put_admin_idm_admins(&rsclient).await;
rsclient
.idm_person_account_primary_credential_set_password(
IDM_ADMIN_TEST_USER,
IDM_ADMIN_TEST_PASSWORD,
)
.await
.expect(&format!("Failed to set {} password", IDM_ADMIN_TEST_USER));
let token_cache_dir = tempdir().unwrap();
let token_cache_path = format!("{}/kanidm_tokens", token_cache_dir.path().display());
// we have to spawn in another thread for ... reasons
assert!(tokio::task::spawn_blocking(move || {
let anon_login = cli_kanidm!()
.args(&["login", "-D", "anonymous"])
.output()
.unwrap();
println!("Login Output: {:?}", anon_login);
let anon_whoami = cli_kanidm!()
.args(&["self", "whoami", "-D", "anonymous"])
.output()
.unwrap();
assert!(anon_whoami.status.success());
println!("Output: {:?}", anon_whoami);
test_cmd_admin(&token_cache_path, &rsclient, "login -D admin");
// login as idm_admin
test_cmd_idm_admin(&token_cache_path, &rsclient, "login -D idm_admin");
test_cmd_admin_split(
&token_cache_path,
&rsclient,
&[
"service-account",
"create",
NOT_ADMIN_TEST_USERNAME,
"Test account",
"-D",
"admin",
"-o",
"json",
],
);
test_cmd_admin(
&token_cache_path,
&rsclient,
&format!("service-account get -D admin {}", NOT_ADMIN_TEST_USERNAME),
);
// updating the display name
test_cmd_admin(
&token_cache_path,
&rsclient,
&format!(
"service-account update -D admin {} --displayname cheeseballs",
NOT_ADMIN_TEST_USERNAME
),
);
// updating the email
test_cmd_admin(
&token_cache_path,
&rsclient,
&format!(
"service-account update -D admin {} --mail foo@bar.com",
NOT_ADMIN_TEST_USERNAME
),
);
// checking the email was changed
let sad = test_cmd_admin(
&token_cache_path,
&rsclient,
&format!(
"service-account get -D admin -o json {}",
NOT_ADMIN_TEST_USERNAME
),
);
let str_output: String = String::from_utf8_lossy(&sad.stdout).into();
assert!(str_output.contains("foo@bar.com"));
true
})
.await
.unwrap());
println!("Success!");
}

View file

@ -70,6 +70,8 @@ features = ["crossterm-backend"]
clap = { workspace = true, features = ["derive"] }
clap_complete = { workspace = true }
kanidm_build_profiles = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
url = { workspace = true }

View file

@ -100,7 +100,7 @@ impl CommonOpt {
async fn try_to_client(&self, optype: OpType) -> Result<KanidmClient, ToClientError> {
let client = self.to_unauth_client();
// Read the token file.
let tokens = match read_tokens() {
let tokens = match read_tokens(&client.get_token_cache_path()) {
Ok(t) => t,
Err(_e) => {
error!("Error retrieving authentication token store");
@ -187,7 +187,7 @@ impl CommonOpt {
} else {
// Unable to automatically select the user because multiple tokens exist
// so we'll prompt the user to select one
match prompt_for_username_get_values() {
match prompt_for_username_get_values(&client.get_token_cache_path()) {
Ok(tuple) => tuple,
Err(msg) => {
error!("Error: {}", msg);
@ -309,8 +309,8 @@ impl CommonOpt {
/// This parses the token store and prompts the user to select their username, returns the username/token as a tuple of Strings
///
/// Used to reduce duplication in implementing [prompt_for_username_get_username] and [prompt_for_username_get_token]
pub fn prompt_for_username_get_values() -> Result<(String, String), String> {
let tokens = match read_tokens() {
pub fn prompt_for_username_get_values(token_cache_path: &str) -> Result<(String, String), String> {
let tokens = match read_tokens(token_cache_path) {
Ok(value) => value,
_ => return Err("Error retrieving authentication token store".to_string()),
};
@ -353,8 +353,8 @@ pub fn prompt_for_username_get_values() -> Result<(String, String), String> {
/// This parses the token store and prompts the user to select their username, returns the username as a String
///
/// Powered by [prompt_for_username_get_values]
pub fn prompt_for_username_get_username() -> Result<String, String> {
match prompt_for_username_get_values() {
pub fn prompt_for_username_get_username(token_cache_path: &str) -> Result<String, String> {
match prompt_for_username_get_values(token_cache_path) {
Ok(value) => {
let (f_user, _) = value;
Ok(f_user)

View file

@ -24,7 +24,7 @@ impl DomainOpt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &opt.copt.output_mode),
Err(e) => handle_client_error(e, opt.copt.output_mode),
}
}
DomainOpt::SetLdapBasedn { copt, new_basedn } => {
@ -35,28 +35,28 @@ impl DomainOpt {
let client = copt.to_client(OpType::Write).await;
match client.idm_domain_set_ldap_basedn(new_basedn).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
DomainOpt::SetLdapAllowUnixPasswordBind { copt, enable } => {
let client = copt.to_client(OpType::Write).await;
match client.idm_set_ldap_allow_unix_password_bind(*enable).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
DomainOpt::Show(copt) => {
let client = copt.to_client(OpType::Read).await;
match client.idm_domain_get().await {
Ok(e) => println!("{}", e),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
DomainOpt::ResetTokenKey(copt) => {
let client = copt.to_client(OpType::Write).await;
match client.idm_domain_reset_token_key().await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
}

View file

@ -15,7 +15,7 @@ impl GroupAccountPolicyOpt {
GroupAccountPolicyOpt::Enable { name, copt } => {
let client = copt.to_client(OpType::Write).await;
if let Err(e) = client.group_account_policy_enable(name).await {
handle_client_error(e, &copt.output_mode);
handle_client_error(e, copt.output_mode);
} else {
println!("Group enabled for account policy.");
}
@ -26,7 +26,7 @@ impl GroupAccountPolicyOpt {
.group_account_policy_authsession_expiry_set(name, *expiry)
.await
{
handle_client_error(e, &copt.output_mode);
handle_client_error(e, copt.output_mode);
} else {
println!("Updated authsession expiry.");
}
@ -37,7 +37,7 @@ impl GroupAccountPolicyOpt {
.group_account_policy_privilege_expiry_set(name, *expiry)
.await
{
handle_client_error(e, &copt.output_mode);
handle_client_error(e, copt.output_mode);
} else {
println!("Updated authsession expiry.");
}

View file

@ -38,7 +38,7 @@ impl GroupOpt {
}
OutputMode::Text => r.iter().for_each(|ent| println!("{}", ent)),
},
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
GroupOpt::Get(gcopt) => {
@ -55,7 +55,7 @@ impl GroupOpt {
OutputMode::Text => println!("{}", e),
},
Ok(None) => warn!("No matching group '{}'", gcopt.name.as_str()),
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
}
}
GroupOpt::Create(gcopt) => {
@ -70,14 +70,14 @@ impl GroupOpt {
GroupOpt::Delete(gcopt) => {
let client = gcopt.copt.to_client(OpType::Write).await;
match client.idm_group_delete(gcopt.name.as_str()).await {
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
Ok(_) => println!("Successfully deleted group {}", gcopt.name.as_str()),
}
}
GroupOpt::PurgeMembers(gcopt) => {
let client = gcopt.copt.to_client(OpType::Write).await;
match client.idm_group_purge_members(gcopt.name.as_str()).await {
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
Ok(_) => println!(
"Successfully purged members of group {}",
gcopt.name.as_str()
@ -89,7 +89,7 @@ impl GroupOpt {
match client.idm_group_get_members(gcopt.name.as_str()).await {
Ok(Some(groups)) => groups.iter().for_each(|m| println!("{:?}", m)),
Ok(None) => warn!("No members in group {}", gcopt.name.as_str()),
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
}
}
GroupOpt::AddMembers(gcopt) => {
@ -100,7 +100,7 @@ impl GroupOpt {
.idm_group_add_members(gcopt.name.as_str(), &new_members)
.await
{
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
Ok(_) => println!(
"Successfully added {:?} to group \"{}\"",
&new_members,
@ -119,7 +119,7 @@ impl GroupOpt {
{
Err(e) => {
error!("Failed to remove members!");
handle_client_error(e, &gcopt.copt.output_mode)
handle_client_error(e, gcopt.copt.output_mode)
}
Ok(_) => println!("Successfully removed members from {}", gcopt.name.as_str()),
}
@ -133,7 +133,7 @@ impl GroupOpt {
.idm_group_set_members(gcopt.name.as_str(), &new_members)
.await
{
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
Ok(_) => println!("Successfully set members for group {}", gcopt.name.as_str()),
}
}
@ -142,7 +142,7 @@ impl GroupOpt {
let client = gcopt.copt.to_client(OpType::Read).await;
match client.idm_group_unix_token_get(gcopt.name.as_str()).await {
Ok(token) => println!("{}", token),
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
}
}
GroupPosix::Set(gcopt) => {
@ -151,7 +151,7 @@ impl GroupOpt {
.idm_group_unix_extend(gcopt.name.as_str(), gcopt.gidnumber)
.await
{
Err(e) => handle_client_error(e, &gcopt.copt.output_mode),
Err(e) => handle_client_error(e, gcopt.copt.output_mode),
Ok(_) => println!(
"Success adding POSIX configuration for group {}",
gcopt.name.as_str()

View file

@ -43,7 +43,7 @@ mod system_config;
mod webauthn;
/// Throws an error and exits the program when we get an error
pub(crate) fn handle_client_error(response: ClientError, _output_mode: &OutputMode) {
pub(crate) fn handle_client_error(response: ClientError, _output_mode: OutputMode) {
match response {
ClientError::Http(status, error, message) => {
let error_msg = match error {
@ -101,7 +101,7 @@ impl SelfOpt {
}
}
}
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SelfOpt::IdentifyUser(copt) => {

View file

@ -46,7 +46,7 @@ impl Oauth2Opt {
}
OutputMode::Text => r.iter().for_each(|ent| println!("{}", ent)),
},
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
Oauth2Opt::Get(nopt) => {
@ -54,7 +54,7 @@ impl Oauth2Opt {
match client.idm_oauth2_rs_get(nopt.name.as_str()).await {
Ok(Some(e)) => println!("{}", e),
Ok(None) => println!("No matching entries"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::CreateBasic {
@ -73,7 +73,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
Oauth2Opt::CreatePublic {
@ -92,7 +92,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
Oauth2Opt::UpdateScopeMap(cbopt) => {
@ -106,7 +106,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &cbopt.nopt.copt.output_mode),
Err(e) => handle_client_error(e, cbopt.nopt.copt.output_mode),
}
}
Oauth2Opt::DeleteScopeMap(cbopt) => {
@ -116,7 +116,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &cbopt.nopt.copt.output_mode),
Err(e) => handle_client_error(e, cbopt.nopt.copt.output_mode),
}
}
Oauth2Opt::UpdateSupScopeMap(cbopt) => {
@ -146,7 +146,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &cbopt.nopt.copt.output_mode),
Err(e) => handle_client_error(e, cbopt.nopt.copt.output_mode),
}
}
Oauth2Opt::ResetSecrets(cbopt) => {
@ -165,7 +165,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &cbopt.copt.output_mode),
Err(e) => handle_client_error(e, cbopt.copt.output_mode),
}
}
Oauth2Opt::ShowBasicSecret(nopt) => {
@ -184,14 +184,14 @@ impl Oauth2Opt {
Ok(None) => {
eprintln!("No secret configured");
}
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::Delete(nopt) => {
let client = nopt.copt.to_client(OpType::Write).await;
match client.idm_oauth2_rs_delete(nopt.name.as_str()).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::SetDisplayname(cbopt) => {
@ -210,7 +210,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &cbopt.nopt.copt.output_mode),
Err(e) => handle_client_error(e, cbopt.nopt.copt.output_mode),
}
}
Oauth2Opt::SetName { nopt, name } => {
@ -229,7 +229,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::SetLandingUrl { nopt, url } => {
@ -248,21 +248,21 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::EnablePkce(nopt) => {
let client = nopt.copt.to_client(OpType::Write).await;
match client.idm_oauth2_rs_enable_pkce(nopt.name.as_str()).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::DisablePkce(nopt) => {
let client = nopt.copt.to_client(OpType::Write).await;
match client.idm_oauth2_rs_disable_pkce(nopt.name.as_str()).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::EnableLegacyCrypto(nopt) => {
@ -272,7 +272,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::DisableLegacyCrypto(nopt) => {
@ -282,7 +282,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::PreferShortUsername(nopt) => {
@ -292,7 +292,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
Oauth2Opt::PreferSPNUsername(nopt) => {
@ -302,7 +302,7 @@ impl Oauth2Opt {
.await
{
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
}

View file

@ -83,7 +83,7 @@ impl PersonOpt {
"No RADIUS secret set for user {}",
aopt.aopts.account_id.as_str(),
),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
AccountRadius::Generate(aopt) => {
@ -133,7 +133,7 @@ impl PersonOpt {
.await
{
Ok(token) => println!("{}", token),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
PersonPosix::Set(aopt) => {
@ -146,7 +146,7 @@ impl PersonOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
PersonPosix::SetPassword(aopt) => {
@ -166,7 +166,7 @@ impl PersonOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
}, // end PersonOpt::Posix
@ -186,7 +186,7 @@ impl PersonOpt {
}
}
}
Err(e) => handle_client_error(e, &apo.copt.output_mode),
Err(e) => handle_client_error(e, apo.copt.output_mode),
}
}
AccountUserAuthToken::Destroy {
@ -204,7 +204,7 @@ impl PersonOpt {
}
Err(e) => {
error!("Error destroying account session");
handle_client_error(e, &copt.output_mode);
handle_client_error(e, copt.output_mode);
}
}
}
@ -218,7 +218,7 @@ impl PersonOpt {
.await
{
Ok(pkeys) => pkeys.iter().for_each(|pkey| println!("{}", pkey)),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
AccountSsh::Add(aopt) => {
@ -231,7 +231,7 @@ impl PersonOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
AccountSsh::Delete(aopt) => {
@ -243,7 +243,7 @@ impl PersonOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
}, // end PersonOpt::Ssh
@ -260,7 +260,7 @@ impl PersonOpt {
}
OutputMode::Text => r.iter().for_each(|ent| println!("{}", ent)),
},
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
PersonOpt::Update(aopt) => {
@ -276,7 +276,7 @@ impl PersonOpt {
.await
{
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
PersonOpt::Get(aopt) => {
@ -295,7 +295,7 @@ impl PersonOpt {
OutputMode::Text => println!("{}", e),
},
Ok(None) => println!("No matching entries"),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
PersonOpt::Delete(aopt) => {
@ -321,7 +321,7 @@ impl PersonOpt {
modmessage.status = MessageStatus::Failure;
eprintln!("{}", modmessage);
// handle_client_error(e, &aopt.copt.output_mode),
// handle_client_error(e, aopt.copt.output_mode),
}
Ok(result) => {
debug!("{:?}", result);
@ -345,7 +345,7 @@ impl PersonOpt {
acopt.aopts.account_id.as_str(),
)
}
Err(e) => handle_client_error(e, &acopt.copt.output_mode),
Err(e) => handle_client_error(e, acopt.copt.output_mode),
}
}
PersonOpt::Validity { commands } => match commands {
@ -434,7 +434,7 @@ impl PersonOpt {
}
};
match res {
Err(e) => handle_client_error(e, &ano.copt.output_mode),
Err(e) => handle_client_error(e, ano.copt.output_mode),
_ => println!("Success"),
};
}

View file

@ -16,7 +16,7 @@ impl RecycleOpt {
let client = copt.to_client(OpType::Read).await;
match client.recycle_bin_list().await {
Ok(r) => r.iter().for_each(|e| println!("{}", e)),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
RecycleOpt::Get(nopt) => {
@ -24,13 +24,13 @@ impl RecycleOpt {
match client.recycle_bin_get(nopt.name.as_str()).await {
Ok(Some(e)) => println!("{}", e),
Ok(None) => println!("No matching entries"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
RecycleOpt::Revive(nopt) => {
let client = nopt.copt.to_client(OpType::Write).await;
if let Err(e) = client.recycle_bin_revive(nopt.name.as_str()).await {
handle_client_error(e, &nopt.copt.output_mode)
handle_client_error(e, nopt.copt.output_mode)
}
}
}

View file

@ -192,7 +192,7 @@ impl ServiceAccountOpt {
.await
{
Ok(token) => println!("{}", token),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
ServiceAccountPosix::Set(aopt) => {
@ -205,7 +205,7 @@ impl ServiceAccountOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
}, // end ServiceAccountOpt::Posix
@ -258,7 +258,7 @@ impl ServiceAccountOpt {
.await
{
Ok(pkeys) => pkeys.iter().for_each(|pkey| println!("{}", pkey)),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
AccountSsh::Add(aopt) => {
@ -271,7 +271,7 @@ impl ServiceAccountOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
AccountSsh::Delete(aopt) => {
@ -283,7 +283,7 @@ impl ServiceAccountOpt {
)
.await
{
handle_client_error(e, &aopt.copt.output_mode)
handle_client_error(e, aopt.copt.output_mode)
}
}
}, // end ServiceAccountOpt::Ssh
@ -291,7 +291,7 @@ impl ServiceAccountOpt {
let client = copt.to_client(OpType::Read).await;
match client.idm_service_account_list().await {
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
ServiceAccountOpt::Update(aopt) => {
@ -306,18 +306,18 @@ impl ServiceAccountOpt {
.await
{
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
ServiceAccountOpt::Get(aopt) => {
let client = aopt.copt.to_client(OpType::Read).await;
match client
let res = client
.idm_service_account_get(aopt.aopts.account_id.as_str())
.await
{
Ok(Some(e)) => println!("{}", e),
.await;
match res {
Ok(Some(e)) => aopt.copt.output_mode.print_message(e),
Ok(None) => println!("No matching entries"),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
ServiceAccountOpt::Delete(aopt) => {
@ -358,7 +358,7 @@ impl ServiceAccountOpt {
)
.await
{
handle_client_error(e, &acopt.copt.output_mode)
handle_client_error(e, acopt.copt.output_mode)
}
}
ServiceAccountOpt::Validity { commands } => match commands {
@ -447,7 +447,7 @@ impl ServiceAccountOpt {
}
};
match res {
Err(e) => handle_client_error(e, &ano.copt.output_mode),
Err(e) => handle_client_error(e, ano.copt.output_mode),
_ => println!("Success"),
};
}
@ -462,7 +462,7 @@ impl ServiceAccountOpt {
)
.await
{
Err(e) => handle_client_error(e, &ano.copt.output_mode),
Err(e) => handle_client_error(e, ano.copt.output_mode),
_ => println!("Success"),
}
} else {
@ -480,7 +480,7 @@ impl ServiceAccountOpt {
)
.await
{
Err(e) => handle_client_error(e, &ano.copt.output_mode),
Err(e) => handle_client_error(e, ano.copt.output_mode),
_ => println!("Success"),
}
}
@ -494,7 +494,7 @@ impl ServiceAccountOpt {
.await
{
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &aopt.copt.output_mode),
Err(e) => handle_client_error(e, aopt.copt.output_mode),
}
}
}

View file

@ -1,7 +1,7 @@
use crate::common::OpType;
use std::collections::BTreeMap;
use std::fs::{create_dir, File};
use std::io::{self, BufReader, BufWriter, ErrorKind, Write};
use std::io::{self, BufReader, BufWriter, ErrorKind, IsTerminal, Write};
use std::path::PathBuf;
use std::str::FromStr;
@ -9,6 +9,7 @@ use compact_jwt::{Jws, JwsUnverified};
use dialoguer::theme::ColorfulTheme;
use dialoguer::Select;
use kanidm_client::{ClientError, KanidmClient};
use kanidm_proto::constants::CLIENT_TOKEN_CACHE;
use kanidm_proto::v1::{AuthAllowed, AuthResponse, AuthState, UserAuthToken};
#[cfg(target_family = "unix")]
use libc::umask;
@ -16,18 +17,26 @@ use webauthn_authenticator_rs::prelude::RequestChallengeResponse;
use crate::common::prompt_for_username_get_username;
use crate::webauthn::get_authenticator;
use crate::{LoginOpt, LogoutOpt, ReauthOpt, SessionOpt};
use crate::{CommonOpt, LoginOpt, LogoutOpt, ReauthOpt, SessionOpt};
static TOKEN_DIR: &str = "~/.cache";
static TOKEN_PATH: &str = "~/.cache/kanidm_tokens";
impl CommonOpt {
fn get_token_cache_path(&self) -> String {
match self.token_cache_path.clone() {
None => CLIENT_TOKEN_CACHE.to_string(),
Some(val) => val.clone(),
}
}
}
#[allow(clippy::result_unit_err)]
pub fn read_tokens() -> Result<BTreeMap<String, String>, ()> {
let token_path = PathBuf::from(shellexpand::tilde(TOKEN_PATH).into_owned());
pub fn read_tokens(token_path: &str) -> Result<BTreeMap<String, String>, ()> {
let token_path = PathBuf::from(shellexpand::tilde(token_path).into_owned());
if !token_path.exists() {
debug!(
"Token cache file path {:?} does not exist, returning an empty token store.",
TOKEN_PATH
token_path
);
return Ok(BTreeMap::new());
}
@ -50,7 +59,8 @@ pub fn read_tokens() -> Result<BTreeMap<String, String>, ()> {
_ => {
warn!(
"Cannot read tokens from {} due to error: {:?} ... continuing.",
TOKEN_PATH, e
token_path.display(),
e
);
return Ok(BTreeMap::new());
}
@ -69,9 +79,9 @@ pub fn read_tokens() -> Result<BTreeMap<String, String>, ()> {
}
#[allow(clippy::result_unit_err)]
pub fn write_tokens(tokens: &BTreeMap<String, String>) -> Result<(), ()> {
pub fn write_tokens(tokens: &BTreeMap<String, String>, token_path: &str) -> Result<(), ()> {
let token_dir = PathBuf::from(shellexpand::tilde(TOKEN_DIR).into_owned());
let token_path = PathBuf::from(shellexpand::tilde(TOKEN_PATH).into_owned());
let token_path = PathBuf::from(shellexpand::tilde(token_path).into_owned());
token_dir
.parent()
@ -103,7 +113,7 @@ pub fn write_tokens(tokens: &BTreeMap<String, String>) -> Result<(), ()> {
let file = File::create(&token_path).map_err(|e| {
#[cfg(target_family = "unix")]
let _ = unsafe { umask(before) };
error!("Can not write to {} -> {:?}", TOKEN_PATH, e);
error!("Can not write to {} -> {:?}", token_path.display(), e);
})?;
#[cfg(target_family = "unix")]
@ -301,7 +311,7 @@ async fn process_auth_state(
}
// Read the current tokens
let mut tokens = read_tokens().unwrap_or_else(|_| {
let mut tokens = read_tokens(&client.get_token_cache_path()).unwrap_or_else(|_| {
error!("Error retrieving authentication token store");
std::process::exit(1);
});
@ -333,7 +343,7 @@ async fn process_auth_state(
tokens.insert(spn.clone(), tonk);
// write them out.
if write_tokens(&tokens).is_err() {
if write_tokens(&tokens, &client.get_token_cache_path()).is_err() {
error!("Error persisting authentication token store");
std::process::exit(1);
};
@ -436,13 +446,21 @@ impl LogoutOpt {
let mut _tmp_username = String::new();
match &self.copt.username {
Some(value) => value.clone(),
None => match prompt_for_username_get_username() {
None => {
// check if we're in a tty
if std::io::stdin().is_terminal() {
match prompt_for_username_get_username(&self.copt.get_token_cache_path()) {
Ok(value) => value,
Err(msg) => {
error!("{}", msg);
std::process::exit(1);
}
},
}
} else {
eprintln!("Not running in interactive mode and no username specified, can't continue!");
return;
}
}
}
} else {
let client = self.copt.to_client(OpType::Read).await;
@ -480,7 +498,7 @@ impl LogoutOpt {
uat.spn
};
let mut tokens = read_tokens().unwrap_or_else(|_| {
let mut tokens = read_tokens(&self.copt.get_token_cache_path()).unwrap_or_else(|_| {
error!("Error retrieving authentication token store");
std::process::exit(1);
});
@ -488,7 +506,7 @@ impl LogoutOpt {
// Remove our old one
if tokens.remove(&spn).is_some() {
// write them out.
if let Err(_e) = write_tokens(&tokens) {
if let Err(_e) = write_tokens(&tokens, &self.copt.get_token_cache_path()) {
error!("Error persisting authentication token store");
std::process::exit(1);
};
@ -506,8 +524,8 @@ impl SessionOpt {
}
}
fn read_valid_tokens() -> BTreeMap<String, (String, UserAuthToken)> {
read_tokens()
fn read_valid_tokens(token_cache_path: &str) -> BTreeMap<String, (String, UserAuthToken)> {
read_tokens(token_cache_path)
.unwrap_or_else(|_| {
error!("Error retrieving authentication token store");
std::process::exit(1);
@ -535,15 +553,15 @@ impl SessionOpt {
pub async fn exec(&self) {
match self {
SessionOpt::List(_) => {
let tokens = Self::read_valid_tokens();
SessionOpt::List(copt) => {
let tokens = Self::read_valid_tokens(&copt.get_token_cache_path());
for (_, uat) in tokens.values() {
println!("---");
println!("{}", uat);
}
}
SessionOpt::Cleanup(_) => {
let tokens = Self::read_valid_tokens();
SessionOpt::Cleanup(copt) => {
let tokens = Self::read_valid_tokens(&copt.get_token_cache_path());
let start_len = tokens.len();
let now = time::OffsetDateTime::now_utc();
@ -566,7 +584,7 @@ impl SessionOpt {
let end_len = tokens.len();
if let Err(_e) = write_tokens(&tokens) {
if let Err(_e) = write_tokens(&tokens, &copt.get_token_cache_path()) {
error!("Error persisting authentication token store");
std::process::exit(1);
};

View file

@ -25,7 +25,7 @@ impl SynchOpt {
match client.idm_sync_account_list().await {
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::Get(nopt) => {
@ -33,7 +33,7 @@ impl SynchOpt {
match client.idm_sync_account_get(nopt.name.as_str()).await {
Ok(Some(e)) => println!("{}", e),
Ok(None) => println!("No matching entries"),
Err(e) => handle_client_error(e, &nopt.copt.output_mode),
Err(e) => handle_client_error(e, nopt.copt.output_mode),
}
}
SynchOpt::SetCredentialPortal {
@ -47,7 +47,7 @@ impl SynchOpt {
.await
{
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::Create {
@ -61,7 +61,7 @@ impl SynchOpt {
.await
{
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::GenerateToken {
@ -75,14 +75,14 @@ impl SynchOpt {
.await
{
Ok(token) => println!("token: {}", token),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::DestroyToken { account_id, copt } => {
let client = copt.to_client(OpType::Write).await;
match client.idm_sync_account_destroy_token(account_id).await {
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::SetYieldAttributes {
@ -96,14 +96,14 @@ impl SynchOpt {
.await
{
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::ForceRefresh { account_id, copt } => {
let client = copt.to_client(OpType::Write).await;
match client.idm_sync_account_force_refresh(account_id).await {
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::Finalise { account_id, copt } => {
@ -120,7 +120,7 @@ impl SynchOpt {
let client = copt.to_client(OpType::Write).await;
match client.idm_sync_account_finalise(account_id).await {
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
SynchOpt::Terminate { account_id, copt } => {
@ -137,7 +137,7 @@ impl SynchOpt {
let client = copt.to_client(OpType::Write).await;
match client.idm_sync_account_terminate(account_id).await {
Ok(()) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
}

View file

@ -30,7 +30,7 @@ impl PwBadlistOpt {
eprintln!("--");
eprintln!("Success");
}
Err(e) => crate::handle_client_error(e, &copt.output_mode),
Err(e) => crate::handle_client_error(e, copt.output_mode),
}
}
PwBadlistOpt::Upload {
@ -126,7 +126,7 @@ impl PwBadlistOpt {
let client = copt.to_client(OpType::Write).await;
match client.system_password_badlist_append(filt_pwset).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
} // End Upload
@ -165,7 +165,7 @@ impl PwBadlistOpt {
match client.system_password_badlist_remove(pwset).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
} // End Remove
}

View file

@ -23,7 +23,7 @@ impl DeniedNamesOpt {
eprintln!("--");
eprintln!("Success");
}
Err(e) => crate::handle_client_error(e, &copt.output_mode),
Err(e) => crate::handle_client_error(e, copt.output_mode),
}
}
DeniedNamesOpt::Append { copt, names } => {
@ -31,7 +31,7 @@ impl DeniedNamesOpt {
match client.system_denied_names_append(names).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
DeniedNamesOpt::Remove { copt, names } => {
@ -39,7 +39,7 @@ impl DeniedNamesOpt {
match client.system_denied_names_remove(names).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
Err(e) => handle_client_error(e, copt.output_mode),
}
}
}

View file

@ -14,7 +14,7 @@ pub struct DebugOpt {
pub debug: bool,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
/// The CLI output mode, either text or json, falls back to text if you ask for something other than text/json
pub enum OutputMode {
Text,
@ -32,6 +32,25 @@ impl std::str::FromStr for OutputMode {
}
}
impl OutputMode {
pub fn print_message<T>(self, input: T)
where
T: serde::Serialize + std::fmt::Debug + std::fmt::Display,
{
match self {
OutputMode::Json => {
println!(
"{}",
serde_json::to_string(&input).unwrap_or(format!("{:?}", input))
);
}
OutputMode::Text => {
println!("{}", input);
}
}
}
}
#[derive(Debug, Args, Clone)]
pub struct CommonOpt {
/// Enable debugging of the kanidm tool
@ -56,6 +75,9 @@ pub struct CommonOpt {
default_value_t = false
)]
skip_hostname_verification: bool,
/// Path to a file to cache tokens in, defaults to ~/.cache/kanidm_tokens
#[clap(short, long, env = "KANIDM_TOKEN_CACHE_PATH", hide = true, default_value = None)]
token_cache_path: Option<String>,
}
#[derive(Debug, Args)]
@ -154,8 +176,8 @@ pub enum GroupOpt {
#[clap(name = "account-policy")]
AccountPolicy {
#[clap(subcommand)]
commands: GroupAccountPolicyOpt
}
commands: GroupAccountPolicyOpt,
},
}
#[derive(Debug, Args)]
@ -597,10 +619,10 @@ pub struct LogoutOpt {
pub enum SessionOpt {
#[clap(name = "list")]
/// List current active sessions
List(DebugOpt),
List(CommonOpt),
#[clap(name = "cleanup")]
/// Remove sessions that have expired or are invalid.
Cleanup(DebugOpt),
Cleanup(CommonOpt),
}
#[derive(Debug, Args)]
@ -853,7 +875,6 @@ pub enum DeniedNamesOpt {
},
}
#[derive(Debug, Subcommand)]
pub enum DomainOpt {
#[clap[name = "set-display-name"]]