mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 04:27:02 +01:00
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:
parent
8cd999d342
commit
3bfc347c53
1
.github/workflows/rust_build.yml
vendored
1
.github/workflows/rust_build.yml
vendored
|
@ -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
100
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -4,7 +4,7 @@ struct CommonOpt {
|
|||
#[clap(short, long = "config", env = "KANIDM_CONFIG")]
|
||||
config_path: Option<PathBuf>,
|
||||
/// Log format (still in very early development)
|
||||
#[clap(short, long = "output", env = "KANIDM_OUTPUT", default_value="text")]
|
||||
#[clap(short, long = "output", env = "KANIDM_OUTPUT", default_value = "text")]
|
||||
output_mode: String,
|
||||
}
|
||||
|
||||
|
@ -60,14 +60,13 @@ struct DbScanListIndex {
|
|||
commonopts: CommonOpt,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug,Parser)]
|
||||
#[derive(Debug, Parser)]
|
||||
struct HealthCheckArgs {
|
||||
/// Disable TLS verification
|
||||
#[clap(short, long, action)]
|
||||
verify_tls: bool,
|
||||
/// Check the 'origin' URL from the server configuration file, instead of the 'address'
|
||||
#[clap(short='O', long, action)]
|
||||
#[clap(short = 'O', long, action)]
|
||||
check_origin: bool,
|
||||
#[clap(flatten)]
|
||||
commonopts: CommonOpt,
|
||||
|
@ -126,7 +125,7 @@ enum DbScanOpt {
|
|||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name="kanidmd")]
|
||||
#[command(name = "kanidmd")]
|
||||
struct KanidmdParser {
|
||||
#[command(subcommand)]
|
||||
commands: KanidmdOpt,
|
||||
|
@ -199,6 +198,6 @@ enum KanidmdOpt {
|
|||
HealthCheck(HealthCheckArgs),
|
||||
|
||||
/// Print the program version and exit
|
||||
#[clap(name="version")]
|
||||
Version(CommonOpt)
|
||||
#[clap(name = "version")]
|
||||
Version(CommonOpt),
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"]]
|
||||
|
|
Loading…
Reference in a new issue