mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Automatically login & reauth (#1691)
* Automatically login & reauth * Revert automatical format by vscode * Only ask user to choose username once * Use dialoguer::Confirm; fix logic
This commit is contained in:
parent
5ecc5dffa0
commit
f4b355c299
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -316,6 +316,17 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-recursion"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-session"
|
name = "async-session"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
@ -2388,6 +2399,7 @@ dependencies = [
|
||||||
name = "kanidm_tools"
|
name = "kanidm_tools"
|
||||||
version = "1.1.0-beta.13-dev"
|
version = "1.1.0-beta.13-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-recursion",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"compact_jwt",
|
"compact_jwt",
|
||||||
|
|
|
@ -38,6 +38,7 @@ homepage = "https://github.com/kanidm/kanidm/"
|
||||||
repository = "https://github.com/kanidm/kanidm/"
|
repository = "https://github.com/kanidm/kanidm/"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
async-recursion = "1.0.4"
|
||||||
async-trait = "^0.1.68"
|
async-trait = "^0.1.68"
|
||||||
base32 = "^0.4.0"
|
base32 = "^0.4.0"
|
||||||
base64 = "^0.21.0"
|
base64 = "^0.21.0"
|
||||||
|
|
|
@ -30,6 +30,7 @@ name = "kanidm_ssh_authorizedkeys_direct"
|
||||||
path = "src/ssh_authorizedkeys.rs"
|
path = "src/ssh_authorizedkeys.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-recursion.workplace = true
|
||||||
clap = { workspace = true, features = ["derive", "env"] }
|
clap = { workspace = true, features = ["derive", "env"] }
|
||||||
compact_jwt = { workspace = true, features = ["openssl"] }
|
compact_jwt = { workspace = true, features = ["openssl"] }
|
||||||
dialoguer.workspace = true
|
dialoguer.workspace = true
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use async_recursion::async_recursion;
|
||||||
use compact_jwt::{Jws, JwsUnverified};
|
use compact_jwt::{Jws, JwsUnverified};
|
||||||
use dialoguer::theme::ColorfulTheme;
|
use dialoguer::theme::ColorfulTheme;
|
||||||
use dialoguer::Select;
|
use dialoguer::{Confirm, Select};
|
||||||
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
||||||
use kanidm_proto::constants::{DEFAULT_CLIENT_CONFIG_PATH, DEFAULT_CLIENT_CONFIG_PATH_HOME};
|
use kanidm_proto::constants::{DEFAULT_CLIENT_CONFIG_PATH, DEFAULT_CLIENT_CONFIG_PATH_HOME};
|
||||||
use kanidm_proto::v1::UserAuthToken;
|
use kanidm_proto::v1::UserAuthToken;
|
||||||
|
|
||||||
use crate::session::read_tokens;
|
use crate::session::read_tokens;
|
||||||
use crate::CommonOpt;
|
use crate::{CommonOpt, LoginOpt, ReauthOpt};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum OpType {
|
pub enum OpType {
|
||||||
Read,
|
Read,
|
||||||
Write,
|
Write,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ToClientError {
|
||||||
|
NeedLogin(String),
|
||||||
|
NeedReauth(String),
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
impl CommonOpt {
|
impl CommonOpt {
|
||||||
pub fn to_unauth_client(&self) -> KanidmClient {
|
pub fn to_unauth_client(&self) -> KanidmClient {
|
||||||
let config_path: String = shellexpand::tilde(DEFAULT_CLIENT_CONFIG_PATH_HOME).into_owned();
|
let config_path: String = shellexpand::tilde(DEFAULT_CLIENT_CONFIG_PATH_HOME).into_owned();
|
||||||
|
@ -73,14 +83,14 @@ impl CommonOpt {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn to_client(&self, optype: OpType) -> KanidmClient {
|
async fn try_to_client(&self, optype: OpType) -> Result<KanidmClient, ToClientError> {
|
||||||
let client = self.to_unauth_client();
|
let client = self.to_unauth_client();
|
||||||
// Read the token file.
|
// Read the token file.
|
||||||
let tokens = match read_tokens() {
|
let tokens = match read_tokens() {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!("Error retrieving authentication token store");
|
error!("Error retrieving authentication token store");
|
||||||
std::process::exit(1);
|
return Err(ToClientError::Other);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,18 +98,22 @@ impl CommonOpt {
|
||||||
error!(
|
error!(
|
||||||
"No valid authentication tokens found. Please login with the 'login' subcommand."
|
"No valid authentication tokens found. Please login with the 'login' subcommand."
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
return Err(ToClientError::Other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need to store guessed username for login and reauth.
|
||||||
|
let username;
|
||||||
|
|
||||||
// If we have a username, use that to select tokens
|
// If we have a username, use that to select tokens
|
||||||
let token = match &self.username {
|
let token = match &self.username {
|
||||||
Some(username) => {
|
Some(_username) => {
|
||||||
|
username = _username.clone();
|
||||||
// Is it in the store?
|
// Is it in the store?
|
||||||
match tokens.get(username) {
|
match tokens.get(&username) {
|
||||||
Some(t) => t.clone(),
|
Some(t) => t.clone(),
|
||||||
None => {
|
None => {
|
||||||
error!("No valid authentication tokens found for {}.", username);
|
error!("No valid authentication tokens found for {}.", username);
|
||||||
std::process::exit(1);
|
return Err(ToClientError::NeedLogin(username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,12 +123,16 @@ impl CommonOpt {
|
||||||
let (f_uname, f_token) = tokens.iter().next().expect("Memory Corruption");
|
let (f_uname, f_token) = tokens.iter().next().expect("Memory Corruption");
|
||||||
// else pick the first token
|
// else pick the first token
|
||||||
debug!("Using cached token for name {}", f_uname);
|
debug!("Using cached token for name {}", f_uname);
|
||||||
|
username = f_uname.clone();
|
||||||
f_token.clone()
|
f_token.clone()
|
||||||
} else {
|
} else {
|
||||||
// Unable to automatically select the user because multiple tokens exist
|
// Unable to automatically select the user because multiple tokens exist
|
||||||
// so we'll prompt the user to select one
|
// so we'll prompt the user to select one
|
||||||
match prompt_for_username_get_token() {
|
match prompt_for_username_get_values() {
|
||||||
Ok(value) => value,
|
Ok((f_uname, f_token)) => {
|
||||||
|
username = f_uname;
|
||||||
|
f_token
|
||||||
|
}
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
error!("{}", msg);
|
error!("{}", msg);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
@ -128,7 +146,7 @@ impl CommonOpt {
|
||||||
Ok(jwtu) => jwtu,
|
Ok(jwtu) => jwtu,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to parse token - {:?}", e);
|
error!("Unable to parse token - {:?}", e);
|
||||||
std::process::exit(1);
|
return Err(ToClientError::Other);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,7 +163,7 @@ impl CommonOpt {
|
||||||
"Session has expired for {} - you may need to login again.",
|
"Session has expired for {} - you may need to login again.",
|
||||||
uat.spn
|
uat.spn
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
return Err(ToClientError::NeedLogin(username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +176,7 @@ impl CommonOpt {
|
||||||
"Privileges have expired for {} - you need to re-authenticate again.",
|
"Privileges have expired for {} - you need to re-authenticate again.",
|
||||||
uat.spn
|
uat.spn
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
return Err(ToClientError::NeedReauth(username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,14 +184,67 @@ impl CommonOpt {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to read token for requested user - you may need to login again.");
|
error!("Unable to read token for requested user - you may need to login again.");
|
||||||
debug!(?e, "JWT Error");
|
debug!(?e, "JWT Error");
|
||||||
std::process::exit(1);
|
return Err(ToClientError::NeedLogin(username));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set it into the client
|
// Set it into the client
|
||||||
client.set_token(token).await;
|
client.set_token(token).await;
|
||||||
|
|
||||||
client
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn to_client(&self, optype: OpType) -> KanidmClient {
|
||||||
|
match self.try_to_client(optype.clone()).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
ToClientError::NeedLogin(username) => {
|
||||||
|
if !Confirm::new()
|
||||||
|
.with_prompt("Would you like to login again?")
|
||||||
|
.interact()
|
||||||
|
.expect("Failed to interact with interactive session")
|
||||||
|
{
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let mut copt = self.clone();
|
||||||
|
copt.username = Some(username);
|
||||||
|
let login_opt = LoginOpt {
|
||||||
|
copt,
|
||||||
|
password: env::var("KANIDM_PASSWORD").ok(),
|
||||||
|
};
|
||||||
|
login_opt.exec().await;
|
||||||
|
// we still use `to_client` instead of `try_to_client` because we may need to prompt user to re-auth again.
|
||||||
|
// since reauth_opt will call `to_client`, this function is recursive anyway.
|
||||||
|
// we use copt since it's username is updated.
|
||||||
|
return login_opt.copt.to_client(optype).await;
|
||||||
|
}
|
||||||
|
ToClientError::NeedReauth(username) => {
|
||||||
|
if !Confirm::new()
|
||||||
|
.with_prompt("Would you like to re-authenticate?")
|
||||||
|
.interact()
|
||||||
|
.expect("Failed to interact with interactive session")
|
||||||
|
{
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let mut copt = self.clone();
|
||||||
|
copt.username = Some(username);
|
||||||
|
let reauth_opt = ReauthOpt { copt };
|
||||||
|
// calls `to_client` recursively
|
||||||
|
// but should not goes into `NeedLogin` branch again
|
||||||
|
reauth_opt.exec().await;
|
||||||
|
if let Ok(c) = reauth_opt.copt.try_to_client(optype).await {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToClientError::Other => {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub struct DebugOpt {
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args, Clone)]
|
||||||
pub struct CommonOpt {
|
pub struct CommonOpt {
|
||||||
/// Enable debbuging of the kanidm tool
|
/// Enable debbuging of the kanidm tool
|
||||||
#[clap(short, long, env = "KANIDM_DEBUG")]
|
#[clap(short, long, env = "KANIDM_DEBUG")]
|
||||||
|
@ -29,14 +29,14 @@ pub struct CommonOpt {
|
||||||
#[clap(parse(from_os_str), short = 'C', long = "ca", env = "KANIDM_CA_PATH")]
|
#[clap(parse(from_os_str), short = 'C', long = "ca", env = "KANIDM_CA_PATH")]
|
||||||
pub ca_path: Option<PathBuf>,
|
pub ca_path: Option<PathBuf>,
|
||||||
/// Log format (still in very early development)
|
/// 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,
|
output_mode: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct GroupNamedMembers {
|
pub struct GroupNamedMembers {
|
||||||
name: String,
|
name: String,
|
||||||
#[clap(required=true,min_values=1)]
|
#[clap(required = true, min_values = 1)]
|
||||||
members: Vec<String>,
|
members: Vec<String>,
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
copt: CommonOpt,
|
copt: CommonOpt,
|
||||||
|
@ -503,7 +503,7 @@ pub enum RecycleOpt {
|
||||||
pub struct LoginOpt {
|
pub struct LoginOpt {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
copt: CommonOpt,
|
copt: CommonOpt,
|
||||||
#[clap(short, long, env="KANIDM_PASSWORD", hide=true)]
|
#[clap(short, long, env = "KANIDM_PASSWORD", hide = true)]
|
||||||
/// Supply a password to the login option
|
/// Supply a password to the login option
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -711,7 +711,6 @@ pub struct OptSetDomainDisplayName {
|
||||||
new_display_name: String,
|
new_display_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum PwBadlistOpt {
|
pub enum PwBadlistOpt {
|
||||||
#[clap[name = "show"]]
|
#[clap[name = "show"]]
|
||||||
|
@ -724,7 +723,7 @@ pub enum PwBadlistOpt {
|
||||||
Upload {
|
Upload {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
copt: CommonOpt,
|
copt: CommonOpt,
|
||||||
#[clap(parse(from_os_str),required=true,min_values=1)]
|
#[clap(parse(from_os_str), required = true, min_values = 1)]
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
/// Perform a dry run and display the list that would have been uploaded instead.
|
/// Perform a dry run and display the list that would have been uploaded instead.
|
||||||
#[clap(short = 'n', long)]
|
#[clap(short = 'n', long)]
|
||||||
|
@ -736,9 +735,9 @@ pub enum PwBadlistOpt {
|
||||||
Remove {
|
Remove {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
copt: CommonOpt,
|
copt: CommonOpt,
|
||||||
#[clap(parse(from_os_str), required=true, min_values=1)]
|
#[clap(parse(from_os_str), required = true, min_values = 1)]
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
|
@ -863,7 +862,7 @@ pub enum SystemOpt {
|
||||||
Synch {
|
Synch {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
commands: SynchOpt,
|
commands: SynchOpt,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
|
|
Loading…
Reference in a new issue