diff --git a/rust/agama-cli/src/auth.rs b/rust/agama-cli/src/auth.rs index 80823d96a6..7bc1c1ad3a 100644 --- a/rust/agama-cli/src/auth.rs +++ b/rust/agama-cli/src/auth.rs @@ -1,13 +1,15 @@ -use clap::{arg, Args, Subcommand}; +use clap::Subcommand; use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; use std::fs; use std::fs::File; -use std::io; +use std::io::{self, IsTerminal}; use std::io::{BufRead, BufReader}; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; +use crate::error::CliError; + const DEFAULT_JWT_FILE: &str = ".agama/agama-jwt"; const DEFAULT_AGAMA_TOKEN_FILE: &str = "/run/agama/token"; const DEFAULT_AUTH_URL: &str = "http://localhost/api/auth"; @@ -15,20 +17,21 @@ const DEFAULT_FILE_MODE: u32 = 0o600; #[derive(Subcommand, Debug)] pub enum AuthCommands { - /// Login with defined server. Result is JWT stored locally and made available to - /// further use. Password can be provided by commandline option, from a file or it fallbacks - /// into an interactive prompt. - Login(LoginArgs), - /// Release currently stored JWT + /// Authenticate with Agama's server and store the credentials + /// + /// It reads the password from the standard input. If it is not available, + /// it asks the user. + Login, + /// Deauthenticate by removing the credentials Logout, - /// Prints currently stored JWT to stdout + /// Prints currently stored credentials to the standard output Show, } /// Main entry point called from agama CLI main loop pub async fn run(subcommand: AuthCommands) -> anyhow::Result<()> { match subcommand { - AuthCommands::Login(options) => login(LoginArgs::proceed(options).password()?).await, + AuthCommands::Login => login(read_password()?).await, AuthCommands::Logout => logout(), AuthCommands::Show => show(), } @@ -56,65 +59,20 @@ pub fn jwt() -> anyhow::Result { Err(anyhow::anyhow!("Authentication token not available")) } -/// Stores user provided configuration for login command -#[derive(Args, Debug)] -pub struct LoginArgs { - #[arg(long, short = 'p')] - password: Option, - #[arg(long, short = 'f')] - file: Option, -} - -impl LoginArgs { - /// Transforms user provided options into internal representation - /// See Credentials trait - fn proceed(options: LoginArgs) -> Box { - if let Some(password) = options.password { - Box::new(KnownCredentials { password }) - } else if let Some(path) = options.file { - Box::new(FileCredentials { path }) - } else { - Box::new(MissingCredentials {}) - } - } -} - -/// Placeholder for no configuration provided by user -struct MissingCredentials; - -/// Stores whatever is needed for reading credentials from a file -struct FileCredentials { - path: PathBuf, -} - -/// Stores credentials as provided by the user directly -struct KnownCredentials { - password: String, -} - -/// Transforms credentials from user's input into format used internaly -trait Credentials { - fn password(&self) -> io::Result; -} - -impl Credentials for KnownCredentials { - fn password(&self) -> io::Result { - Ok(self.password.clone()) - } -} - -impl Credentials for FileCredentials { - fn password(&self) -> io::Result { - read_line_from_file(self.path.as_path()) - } -} - -impl Credentials for MissingCredentials { - fn password(&self) -> io::Result { - let password = read_credential("Password".to_string())?; - - Ok(password) - } +/// Reads the password +/// +/// It reads the password from stdin if available; otherwise, it asks the +/// user. +fn read_password() -> Result { + let stdin = io::stdin(); + let password = if stdin.is_terminal() { + rpassword::prompt_password("Please, introduce the root password: ")? + } else { + let mut buffer = String::new(); + stdin.read_line(&mut buffer)?; + buffer + }; + Ok(password) } /// Path to file where JWT is stored @@ -151,20 +109,6 @@ fn read_line_from_file(path: &Path) -> io::Result { )) } -/// Asks user to provide a line of input. Displays a prompt. -fn read_credential(caption: String) -> io::Result { - let caption = format!("{}: ", caption); - let cred = rpassword::prompt_password(caption.clone()).unwrap(); - if cred.is_empty() { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Failed to read {}", caption), - )); - } - - Ok(cred) -} - /// Sets the archive owner to root:root. Also sets the file permissions to read/write for the /// owner only. fn set_file_permissions(file: &Path) -> io::Result<()> { diff --git a/rust/agama-cli/src/error.rs b/rust/agama-cli/src/error.rs index e7e22baadd..235d5f7c92 100644 --- a/rust/agama-cli/src/error.rs +++ b/rust/agama-cli/src/error.rs @@ -10,4 +10,6 @@ pub enum CliError { InstallationError, #[error("Missing the '=' separator in '{0}'")] MissingSeparator(String), + #[error("Could not read the password: {0}")] + MissingPassword(#[from] std::io::Error), } diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 9121879723..7d29bb8d4a 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon May 27 14:11:55 UTC 2024 - Imobach Gonzalez Sosa + +- The "agama auth" command reads the password from the standard + input (gh#openSUSE/agama#1265). + ------------------------------------------------------------------- Mon May 27 05:49:46 UTC 2024 - Imobach Gonzalez Sosa