Aws s3 connection parameters extension (#89)

* Aws s3 connection parameters extension

* Changed 'save password?' popup to 'change secrets?'

* missing docs
This commit is contained in:
Christian Visintin
2022-01-04 18:31:58 +01:00
parent 53271966da
commit 7d55563556
25 changed files with 830 additions and 114 deletions

View File

@@ -57,6 +57,11 @@ Released on FIXME:
- migrated application to tui-realm 1.x
- Improved application performance
- Changed the buffer size to **65535** (was 65536) for transfer I/O
- **Aws s3 connection parameters extension** 🦊:
- Added `Access Key` to Aws-s3 connection parameters
- Added `Security Access Key` to Aws-s3 connection parameters
- Added `Security token` to Aws-s3 connection parameters
- Added `Session token` to Aws-s3 connection parameters
- **SSH Config**
- Added `ssh config` parameter in configuration
- It is now possible to specify the ssh configuration file to use

6
Cargo.lock generated
View File

@@ -2021,7 +2021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d"
dependencies = [
"lazy_static",
"parking_lot 0.11.2",
"parking_lot 0.10.2",
"serial_test_derive",
]
@@ -2479,9 +2479,9 @@ dependencies = [
[[package]]
name = "tuirealm"
version = "1.3.0"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e5c7137a0bd92feadea98033a1849fe51c83d23f7761b866e8700a3d6f1de7"
checksum = "8b1919cf08ce9eda3b968870ecab04267d684adc22f2cdc13bb4a3846c89eab4"
dependencies = [
"bitflags 1.3.2",
"crossterm",

View File

@@ -112,11 +112,14 @@ Password can be basically provided through 3 ways when address argument is provi
## Aws S3 credentials 🦊
In order to connect to an Aws S3 bucket you must obviously provide some credentials.
There are basically two ways to achieve this, and as you've probably already noticed you **can't** do that via the authentication form.
There are basically three ways to achieve this.
So these are the ways you can provide the credentials for s3:
1. Use your credentials file: just configure the AWS cli via `aws configure` and your credentials should already be located at `~/.aws/credentials`. In case you're using a profile different from `default`, just provide it in the profile field in the authentication form.
2. **Environment variables**: you can always provide your credentials as environment variables. Keep in mind that these credentials **will always override** the credentials located in the `credentials` file. See how to configure the environment below:
1. Authentication form:
1. You can provide the `access_key` (should be mandatory), the `secret_access_key` (should be mandatory), `security_token` and the `session_token`
2. If you save the s3 connection as a bookmark, these credentials will be saved as an encrypted AES-256/BASE64 string in your bookmarks file (except for the security token and session token which are meant to be temporary credentials).
2. Use your credentials file: just configure the AWS cli via `aws configure` and your credentials should already be located at `~/.aws/credentials`. In case you're using a profile different from `default`, just provide it in the profile field in the authentication form.
3. **Environment variables**: you can always provide your credentials as environment variables. Keep in mind that these credentials **will always override** the credentials located in the `credentials` file. See how to configure the environment below:
These should always be mandatory:

View File

@@ -112,11 +112,14 @@ La contraseña se puede proporcionar básicamente a través de 3 formas cuando s
## Credenciales de AWS S3 🦊
Para conectarse a un bucket de Aws S3, obviamente debe proporcionar algunas credenciales.
Básicamente, hay dos formas de lograr esto, y como probablemente ya hayas notado, **no puedes** hacerlo a través del formulario de autenticación.
Básicamente, hay tres formas de lograr esto.
Entonces, estas son las formas en que puede proporcionar las credenciales para s3:
1. Use su archivo de credenciales: simplemente configure la cli de AWS a través de `aws configure` y sus credenciales ya deberían estar ubicadas en`~/.aws/credentials`. En caso de que esté usando un perfil diferente al "predeterminado", simplemente proporciónelo en el campo de perfil en el formulario de autenticación.
2. **Variables de entorno**: siempre puede proporcionar sus credenciales como variables de entorno. Tenga en cuenta que estas credenciales **siempre anularán** las credenciales ubicadas en el archivo `credentials`. Vea cómo configurar el entorno a continuación:
1. Authentication form:
1. Puede proporcionar la `access_key` (debería ser obligatoria), la `secret_access_kedy` (debería ser obligatoria), el `security_token` y el `session_token`
2. Si guarda la conexión s3 como marcador, estas credenciales se guardarán como una cadena AES-256 / BASE64 cifrada en su archivo de marcadores (excepto el token de seguridad y el token de sesión, que deben ser credenciales temporales).
2. Use su archivo de credenciales: simplemente configure la cli de AWS a través de `aws configure` y sus credenciales ya deberían estar ubicadas en`~/.aws/credentials`. En caso de que esté usando un perfil diferente al "predeterminado", simplemente proporciónelo en el campo de perfil en el formulario de autenticación.
3. **Variables de entorno**: siempre puede proporcionar sus credenciales como variables de entorno. Tenga en cuenta que estas credenciales **siempre anularán** las credenciales ubicadas en el archivo `credentials`. Vea cómo configurar el entorno a continuación:
Estos siempre deben ser obligatorios:

View File

@@ -110,11 +110,14 @@ Le mot de passe peut être fourni de 3 manières lorsque l'argument d'adresse es
## Identifiants AWS S3 🦊
Afin de vous connecter à un compartiment Aws S3, vous devez évidemment fournir des informations d'identification.
Il existe essentiellement deux manières d'y parvenir, et comme vous l'avez probablement déjà remarqué, vous ne pouvez **pas** le faire via le formulaire d'authentification.
Il existe essentiellement trois manières d'y parvenir.
Voici donc les moyens de fournir les informations d'identification pour s3 :
1. Utilisez votre fichier d'informations d'identification : configurez simplement l'AWS cli via `aws configure` et vos informations d'identification doivent déjà se trouver dans `~/.aws/credentials`. Si vous utilisez un profil différent de "default", fournissez-le simplement dans le champ profile du formulaire d'authentification.
2. **Variables d'environnement** : vous pouvez toujours fournir vos informations d'identification en tant que variables d'environnement. Gardez à l'esprit que ces informations d'identification **remplaceront toujours** les informations d'identification situées dans le fichier « credentials ». Voir comment configurer l'environnement ci-dessous :
1. Authentication form:
1. Vous pouvez fournir le `access_key` (devrait être obligatoire), le `secret_access_key` (devrait être obligatoire), `security_token` et le `session_token`
2. Si vous enregistrez la connexion s3 en tant que signet, ces informations d'identification seront enregistrées en tant que chaîne AES-256/BASE64 cryptée dans votre fichier de signets (à l'exception du jeton de sécurité et du jeton de session qui sont censés être des informations d'identification temporaires).
2. Utilisez votre fichier d'informations d'identification : configurez simplement l'AWS cli via `aws configure` et vos informations d'identification doivent déjà se trouver dans `~/.aws/credentials`. Si vous utilisez un profil différent de "default", fournissez-le simplement dans le champ profile du formulaire d'authentification.
3. **Variables d'environnement** : vous pouvez toujours fournir vos informations d'identification en tant que variables d'environnement. Gardez à l'esprit que ces informations d'identification **remplaceront toujours** les informations d'identification situées dans le fichier « credentials ». Voir comment configurer l'environnement ci-dessous :
Ceux-ci devraient toujours être obligatoires:

View File

@@ -107,11 +107,14 @@ Quando si usa l'argomento indirizzo non è possibile fornire la password diretta
## Credenziali Aws S3 🦊
Per connettersi ad un bucket S3 devi come già saprai fornire le credenziali fornite da AWS.
Ci sono due modi per passare queste credenziali a termscp e come avrai già notato **non puoi** farlo dal form di autenticazione.
Questi sono quindi i due modi per passare le chiavi:
Ci sono tre modi per passare queste credenziali a termscp.
Questi sono quindi i tre modi per passare le chiavi:
1. Utilizza il file delle credenziali s3: configurando aws via `aws configure` le tue credenziali dovrebbero già venir salvate in `~/.aws/credentials`. Nel caso tu debba usare un profile diverso da `default`, puoi fornire un profilo diverso nell'authentication form.
2. **Variabili d'ambiente**: nel caso il primo metodo non sia utilizzabile, puoi comunque fornirle come variabili d'ambiente. Considera però che queste variabili sovrascriveranno sempre le credenziali situate nel file credentials. Vediamo come impostarle:
1. Form di autenticazione:
1. Puoi fornire la `access_key` (dovrebbe essere obbligatoria), la `secret_access_key` (dovrebbe essere obbligatoria), il `security_token` ed il `session_token`
2. Se salvi la connessione s3 come segnalibro e decidi di salvare la password, questi parametri verranno salvati nel file dei segnalibri criptati con AES-256/BASE64; ad eccezion fatta per i due token, che dovrebbero essere credenziali temporanee, quindi inutili da salvare.
2. Utilizza il file delle credenziali s3: configurando aws via `aws configure` le tue credenziali dovrebbero già venir salvate in `~/.aws/credentials`. Nel caso tu debba usare un profile diverso da `default`, puoi fornire un profilo diverso nell'authentication form.
3. **Variabili d'ambiente**: nel caso il primo metodo non sia utilizzabile, puoi comunque fornirle come variabili d'ambiente. Considera però che queste variabili sovrascriveranno sempre le credenziali situate nel file credentials. Vediamo come impostarle:
Queste sono sempre obbligatorie:

View File

@@ -110,11 +110,14 @@ Password can be basically provided through 3 ways when address argument is provi
## Aws S3 credentials 🦊
In order to connect to an Aws S3 bucket you must obviously provide some credentials.
There are basically two ways to achieve this, and as you've probably already noticed you **can't** do that via the authentication form.
There are basically three ways to achieve this:
So these are the ways you can provide the credentials for s3:
1. Use your credentials file: just configure the AWS cli via `aws configure` and your credentials should already be located at `~/.aws/credentials`. In case you're using a profile different from `default`, just provide it in the profile field in the authentication form.
2. **Environment variables**: you can always provide your credentials as environment variables. Keep in mind that these credentials **will always override** the credentials located in the `credentials` file. See how to configure the environment below:
1. Authentication form:
1. You can provide the `access_key` (should be mandatory), the `secret_access_key` (should be mandatory), `security_token` and the `session_token`
2. If you save the s3 connection as a bookmark, these credentials will be saved as an encrypted AES-256/BASE64 string in your bookmarks file (except for the security token and session token which are meant to be temporary credentials).
2. Use your credentials file: just configure the AWS cli via `aws configure` and your credentials should already be located at `~/.aws/credentials`. In case you're using a profile different from `default`, just provide it in the profile field in the authentication form.
3. **Environment variables**: you can always provide your credentials as environment variables. Keep in mind that these credentials **will always override** the credentials located in the `credentials` file. See how to configure the environment below:
These should always be mandatory:

View File

@@ -108,11 +108,13 @@ s3://buckethead@eu-central-1:default:/assets
## Aws S3 凭证
为了连接到 Aws S3 存储桶,您显然必须提供一些凭据。
基本上有两种方法可以实现这一点,而且您可能已经注意到您**不能**通过身份验证表单来做到这一点。
因此,您可以通过以下方式为 s3 提供凭据:
1. 使用您的凭证文件:只需通过`aws configure` 配置AWS cli您的凭证应该已经位于`~/.aws/credentials`。 如果您使用的配置文件不同于“默认”,只需在身份验证表单的配置文件字段中提供它。
2. **环境变量**: 您始终可以将您的凭据作为环境变量提供。 请记住,这些凭据**将始终覆盖**位于 `credentials` 文件中的凭据。 下面看看如何配置环境:
1. 认证形式:
1. 您可以提供 `access_key`(应该是强制性的)、`secret_access_key`(应该是强制性的)、`security_token` 和`session_token`
2. 如果您将 s3 连接保存为书签,这些凭据将在您的书签文件中保存为加密的 AES-256/BASE64 字符串(安全令牌和会话令牌除外,它们是临时凭据)。.
2. 使用您的凭证文件:只需通过`aws configure` 配置AWS cli您的凭证应该已经位于`~/.aws/credentials`。 如果您使用的配置文件不同于“默认”,只需在身份验证表单的配置文件字段中提供它。
3. **环境变量**: 您始终可以将您的凭据作为环境变量提供。 请记住,这些凭据**将始终覆盖**位于 `credentials` 文件中的凭据。 下面看看如何配置环境:
这些应该始终是强制性的:

View File

@@ -66,6 +66,8 @@ pub struct S3Params {
pub bucket: String,
pub region: String,
pub profile: Option<String>,
pub access_key: Option<String>,
pub secret_access_key: Option<String>,
}
// -- impls
@@ -122,6 +124,8 @@ impl From<AwsS3Params> for S3Params {
bucket: params.bucket_name,
region: params.region,
profile: params.profile,
access_key: params.access_key,
secret_access_key: params.secret_access_key,
}
}
}
@@ -129,6 +133,8 @@ impl From<AwsS3Params> for S3Params {
impl From<S3Params> for AwsS3Params {
fn from(params: S3Params) -> Self {
AwsS3Params::new(params.bucket, params.region, params.profile)
.access_key(params.access_key)
.secret_access_key(params.secret_access_key)
}
}
@@ -227,7 +233,11 @@ mod tests {
#[test]
fn bookmark_from_s3_ftparams() {
let params = ProtocolParams::AwsS3(AwsS3Params::new("omar", "eu-west-1", Some("test")));
let params = ProtocolParams::AwsS3(
AwsS3Params::new("omar", "eu-west-1", Some("test"))
.access_key(Some("pippo"))
.secret_access_key(Some("pluto")),
);
let params: FileTransferParams =
FileTransferParams::new(FileTransferProtocol::AwsS3, params);
let bookmark = Bookmark::from(params);
@@ -240,6 +250,8 @@ mod tests {
assert_eq!(s3.bucket.as_str(), "omar");
assert_eq!(s3.region.as_str(), "eu-west-1");
assert_eq!(s3.profile.as_deref().unwrap(), "test");
assert_eq!(s3.access_key.as_deref().unwrap(), "pippo");
assert_eq!(s3.secret_access_key.as_deref().unwrap(), "pluto");
}
#[test]
@@ -272,7 +284,9 @@ mod tests {
s3: Some(S3Params {
bucket: String::from("veeso"),
region: String::from("eu-west-1"),
profile: None,
profile: Some(String::from("default")),
access_key: Some(String::from("pippo")),
secret_access_key: Some(String::from("pluto")),
}),
};
let params = FileTransferParams::from(bookmark);
@@ -280,6 +294,8 @@ mod tests {
let gparams = params.params.s3_params().unwrap();
assert_eq!(gparams.bucket_name.as_str(), "veeso");
assert_eq!(gparams.region.as_str(), "eu-west-1");
assert_eq!(gparams.profile, None);
assert_eq!(gparams.profile.as_deref().unwrap(), "default");
assert_eq!(gparams.access_key.as_deref().unwrap(), "pippo");
assert_eq!(gparams.secret_access_key.as_deref().unwrap(), "pluto");
}
}

View File

@@ -421,6 +421,8 @@ mod tests {
assert_eq!(s3.bucket.as_str(), "veeso");
assert_eq!(s3.region.as_str(), "eu-west-1");
assert_eq!(s3.profile.as_deref().unwrap(), "default");
assert_eq!(s3.access_key.as_deref().unwrap(), "pippo");
assert_eq!(s3.secret_access_key.as_deref().unwrap(), "pluto");
}
#[test]
@@ -470,6 +472,8 @@ mod tests {
bucket: "veeso".to_string(),
region: "eu-west-1".to_string(),
profile: None,
access_key: None,
secret_access_key: None,
}),
},
);
@@ -531,6 +535,8 @@ mod tests {
bucket = "veeso"
region = "eu-west-1"
profile = "default"
access_key = "pippo"
secret_access_key = "pluto"
[recents]
ISO20201215T094000Z = { address = "172.16.104.10", port = 22, protocol = "SCP", username = "root" }

View File

@@ -77,6 +77,18 @@ impl Builder {
if let Some(profile) = params.profile {
client = client.profile(profile);
}
if let Some(access_key) = params.access_key {
client = client.access_key(access_key);
}
if let Some(secret_access_key) = params.secret_access_key {
client = client.secret_access_key(secret_access_key);
}
if let Some(security_token) = params.security_token {
client = client.security_token(security_token);
}
if let Some(session_token) = params.session_token {
client = client.session_token(session_token);
}
client
}
@@ -124,7 +136,7 @@ impl Builder {
/// Make ssh storage from `ConfigClient` if possible, empty otherwise (empty is implicit if degraded)
fn make_ssh_storage(config_client: &ConfigClient) -> SshKeyStorage {
SshKeyStorage::storage_from_config(config_client)
SshKeyStorage::from(config_client)
}
}
@@ -138,7 +150,13 @@ mod test {
#[test]
fn should_build_aws_s3_fs() {
let params = ProtocolParams::AwsS3(AwsS3Params::new("omar", "eu-west-1", Some("test")));
let params = ProtocolParams::AwsS3(
AwsS3Params::new("omar", "eu-west-1", Some("test"))
.access_key(Some("pippo"))
.secret_access_key(Some("pluto"))
.security_token(Some("omar"))
.session_token(Some("gerry-scotti")),
);
let config_client = get_config_client();
let _ = Builder::build(FileTransferProtocol::AwsS3, params, &config_client);
}

View File

@@ -61,6 +61,10 @@ pub struct AwsS3Params {
pub bucket_name: String,
pub region: String,
pub profile: Option<String>,
pub access_key: Option<String>,
pub secret_access_key: Option<String>,
pub security_token: Option<String>,
pub session_token: Option<String>,
}
impl FileTransferParams {
@@ -102,6 +106,7 @@ impl ProtocolParams {
}
}
/// Get a mutable reference to the inner generic protocol params
pub fn mut_generic_params(&mut self) -> Option<&mut GenericProtocolParams> {
match self {
ProtocolParams::Generic(params) => Some(params),
@@ -167,8 +172,36 @@ impl AwsS3Params {
bucket_name: bucket.as_ref().to_string(),
region: region.as_ref().to_string(),
profile: profile.map(|x| x.as_ref().to_string()),
access_key: None,
secret_access_key: None,
security_token: None,
session_token: None,
}
}
/// Construct aws s3 params with provided access key
pub fn access_key<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.access_key = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided secret_access_key
pub fn secret_access_key<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.secret_access_key = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided security_token
pub fn security_token<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.security_token = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided session_token
pub fn session_token<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.session_token = key.map(|x| x.as_ref().to_string());
self
}
}
#[cfg(test)]
@@ -206,11 +239,31 @@ mod test {
}
#[test]
fn params_aws_s3() {
fn should_init_aws_s3_params() {
let params: AwsS3Params = AwsS3Params::new("omar", "eu-west-1", Some("test"));
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_str(), "eu-west-1");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert!(params.access_key.is_none());
assert!(params.secret_access_key.is_none());
assert!(params.security_token.is_none());
assert!(params.session_token.is_none());
}
#[test]
fn should_init_aws_s3_params_with_optionals() {
let params: AwsS3Params = AwsS3Params::new("omar", "eu-west-1", Some("test"))
.access_key(Some("pippo"))
.secret_access_key(Some("pluto"))
.security_token(Some("omar"))
.session_token(Some("gerry-scotti"));
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_str(), "eu-west-1");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert_eq!(params.access_key.as_deref().unwrap(), "pippo");
assert_eq!(params.secret_access_key.as_deref().unwrap(), "pluto");
assert_eq!(params.security_token.as_deref().unwrap(), "omar");
assert_eq!(params.session_token.as_deref().unwrap(), "gerry-scotti");
}
#[test]

View File

@@ -227,7 +227,9 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
fn read_password(run_opts: &mut RunOpts) -> Result<(), String> {
// Initialize client if necessary
if let Some(remote) = run_opts.remote.as_mut() {
// Ask password for generic params
if let Some(mut params) = remote.params.mut_generic_params() {
// Ask password only if generic protocol params
if params.password.is_none() {
// Ask password if unspecified
params.password = match rpassword::read_password_from_tty(Some("Password: ")) {

View File

@@ -167,7 +167,38 @@ impl BookmarksClient {
*pwd = decrypted_pwd;
}
Err(err) => {
error!("Failed to decrypt password for bookmark: {}", err);
error!("Failed to decrypt `password` for bookmark {}: {}", key, err);
}
}
}
// Decrypt AWS-S3 params
if let Some(s3) = entry.s3.as_mut() {
// Access key
if let Some(access_key) = s3.access_key.as_mut() {
match self.decrypt_str(access_key.as_str()) {
Ok(plain) => {
*access_key = plain;
}
Err(err) => {
error!(
"Failed to decrypt `access_key` for bookmark {}: {}",
key, err
);
}
}
}
// Secret access key
if let Some(secret_access_key) = s3.secret_access_key.as_mut() {
match self.decrypt_str(secret_access_key.as_str()) {
Ok(plain) => {
*secret_access_key = plain;
}
Err(err) => {
error!(
"Failed to decrypt `secret_access_key` for bookmark {}: {}",
key, err
);
}
}
}
}
@@ -190,9 +221,13 @@ impl BookmarksClient {
// Make bookmark
info!("Added bookmark {}", name);
let mut host: Bookmark = self.make_bookmark(params);
// If not save_password, set password to `None`
// If not save_password, set secrets to `None`
if !save_password {
host.password = None;
if let Some(s3) = host.s3.as_mut() {
s3.access_key = None;
s3.secret_access_key = None;
}
}
self.hosts.bookmarks.insert(name, host);
}
@@ -221,6 +256,10 @@ impl BookmarksClient {
let mut host: Bookmark = self.make_bookmark(params);
// Null password for recents
host.password = None;
if let Some(s3) = host.s3.as_mut() {
s3.access_key = None;
s3.secret_access_key = None;
}
// Check if duplicated
for (key, value) in &self.hosts.recents {
if *value == host {
@@ -321,6 +360,15 @@ impl BookmarksClient {
if let Some(pwd) = bookmark.password {
bookmark.password = Some(self.encrypt_str(pwd.as_str()));
}
// Encrypt aws s3 params
if let Some(s3) = bookmark.s3.as_mut() {
if let Some(access_key) = s3.access_key.as_mut() {
*access_key = self.encrypt_str(access_key.as_str());
}
if let Some(secret_access_key) = s3.secret_access_key.as_mut() {
*secret_access_key = self.encrypt_str(secret_access_key.as_str());
}
}
bookmark
}
@@ -346,7 +394,7 @@ impl BookmarksClient {
mod tests {
use super::*;
use crate::filetransfer::params::GenericProtocolParams;
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams};
use crate::filetransfer::{FileTransferProtocol, ProtocolParams};
use pretty_assertions::assert_eq;
@@ -441,6 +489,69 @@ mod tests {
assert_eq!(bookmark.4, None);
}
#[test]
fn should_make_s3_bookmark_with_secrets() {
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
// Initialize a new bookmarks client
let mut client: BookmarksClient =
BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
// Add s3 bookmark
client.add_bookmark("my-bucket", make_s3_ftparams(), true);
// Verify bookmark
let bookmark = client.get_bookmark("my-bucket").unwrap();
assert_eq!(bookmark.protocol, FileTransferProtocol::AwsS3);
let params = bookmark.params.s3_params().unwrap();
assert_eq!(params.access_key.as_deref().unwrap(), "pippo");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert_eq!(params.secret_access_key.as_deref().unwrap(), "pluto");
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_str(), "eu-west-1");
}
#[test]
fn should_make_s3_bookmark_without_secrets() {
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
// Initialize a new bookmarks client
let mut client: BookmarksClient =
BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
// Add s3 bookmark
client.add_bookmark("my-bucket", make_s3_ftparams(), false);
// Verify bookmark
let bookmark = client.get_bookmark("my-bucket").unwrap();
assert_eq!(bookmark.protocol, FileTransferProtocol::AwsS3);
let params = bookmark.params.s3_params().unwrap();
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_str(), "eu-west-1");
// secrets
assert_eq!(params.access_key, None);
assert_eq!(params.secret_access_key, None);
}
#[test]
fn should_make_s3_recent() {
let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
// Initialize a new bookmarks client
let mut client: BookmarksClient =
BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
// Add s3 bookmark
client.add_recent(make_s3_ftparams());
// Verify bookmark
let bookmark = client.iter_recents().next().unwrap();
let bookmark = client.get_recent(bookmark).unwrap();
assert_eq!(bookmark.protocol, FileTransferProtocol::AwsS3);
let params = bookmark.params.s3_params().unwrap();
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_str(), "eu-west-1");
// secrets
assert_eq!(params.access_key, None);
assert_eq!(params.secret_access_key, None);
}
#[test]
fn test_system_bookmarks_manipulate_bookmarks() {
@@ -734,6 +845,19 @@ mod tests {
FileTransferParams::new(protocol, params)
}
fn make_s3_ftparams() -> FileTransferParams {
FileTransferParams::new(
FileTransferProtocol::AwsS3,
ProtocolParams::AwsS3(
AwsS3Params::new("omar", "eu-west-1", Some("test"))
.access_key(Some("pippo"))
.secret_access_key(Some("pluto"))
.security_token(Some("omar"))
.session_token(Some("gerry-scotti")),
),
)
}
fn ftparams_to_tup(
params: FileTransferParams,
) -> (String, u16, FileTransferProtocol, String, Option<String>) {

View File

@@ -37,32 +37,6 @@ pub struct SshKeyStorage {
}
impl SshKeyStorage {
/// Create a `SshKeyStorage` starting from a `ConfigClient`
pub fn storage_from_config(cfg_client: &ConfigClient) -> Self {
let mut hosts: HashMap<String, PathBuf> =
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
debug!("Setting up SSH key storage");
// Iterate over keys
for key in cfg_client.iter_ssh_keys() {
match cfg_client.get_ssh_key(key) {
Ok(host) => match host {
Some((addr, username, rsa_key_path)) => {
let key_name: String = Self::make_mapkey(&addr, &username);
hosts.insert(key_name, rsa_key_path);
}
None => continue,
},
Err(err) => {
error!("Failed to get SSH key for {}: {}", key, err);
continue;
}
}
info!("Got SSH key for {}", key);
}
// Return storage
SshKeyStorage { hosts }
}
/// Create an empty ssh key storage; used in case `ConfigClient` is not available
#[cfg(test)]
pub fn empty() -> Self {
@@ -92,6 +66,33 @@ impl SshKeyStorageT for SshKeyStorage {
}
}
impl From<&ConfigClient> for SshKeyStorage {
fn from(cfg_client: &ConfigClient) -> Self {
let mut hosts: HashMap<String, PathBuf> =
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
debug!("Setting up SSH key storage");
// Iterate over keys
for key in cfg_client.iter_ssh_keys() {
match cfg_client.get_ssh_key(key) {
Ok(host) => match host {
Some((addr, username, rsa_key_path)) => {
let key_name: String = Self::make_mapkey(&addr, &username);
hosts.insert(key_name, rsa_key_path);
}
None => continue,
},
Err(err) => {
error!("Failed to get SSH key for {}: {}", key, err);
continue;
}
}
info!("Got SSH key for {}", key);
}
// Return storage
SshKeyStorage { hosts }
}
}
#[cfg(test)]
mod tests {
@@ -113,7 +114,7 @@ mod tests {
.add_ssh_key("192.168.1.31", "pi", "piroporopero")
.is_ok());
// Create ssh key storage
let storage: SshKeyStorage = SshKeyStorage::storage_from_config(&client);
let storage: SshKeyStorage = SshKeyStorage::from(&client);
// Verify key exists
let mut exp_key_path: PathBuf = key_path.clone();
exp_key_path.push("pi@192.168.1.31.key");

View File

@@ -229,5 +229,9 @@ impl AuthActivity {
self.mount_s3_bucket(params.bucket_name.as_str());
self.mount_s3_region(params.region.as_str());
self.mount_s3_profile(params.profile.as_deref().unwrap_or(""));
self.mount_s3_access_key(params.access_key.as_deref().unwrap_or(""));
self.mount_s3_secret_access_key(params.secret_access_key.as_deref().unwrap_or(""));
self.mount_s3_security_token(params.security_token.as_deref().unwrap_or(""));
self.mount_s3_session_token(params.session_token.as_deref().unwrap_or(""));
}
}

View File

@@ -346,7 +346,7 @@ impl BookmarkSavePassword {
.value(0)
.rewind(true)
.foreground(color)
.title("Save password?", Alignment::Center),
.title("Save secrets?", Alignment::Center),
}
}
}

View File

@@ -177,7 +177,7 @@ impl Component<Msg, NoUserEvent> for InputAddress {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -263,7 +263,7 @@ impl Component<Msg, NoUserEvent> for InputPort {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -348,7 +348,7 @@ impl Component<Msg, NoUserEvent> for InputUsername {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -432,7 +432,7 @@ impl Component<Msg, NoUserEvent> for InputPassword {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -517,7 +517,7 @@ impl Component<Msg, NoUserEvent> for InputS3Bucket {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -537,7 +537,7 @@ impl Component<Msg, NoUserEvent> for InputS3Bucket {
}
}
// -- s3 bucket
// -- s3 region
#[derive(MockComponent)]
pub struct InputS3Region {
@@ -602,7 +602,7 @@ impl Component<Msg, NoUserEvent> for InputS3Region {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -622,7 +622,7 @@ impl Component<Msg, NoUserEvent> for InputS3Region {
}
}
// -- s3 bucket
// -- s3 profile
#[derive(MockComponent)]
pub struct InputS3Profile {
@@ -687,7 +687,7 @@ impl Component<Msg, NoUserEvent> for InputS3Profile {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -708,3 +708,342 @@ impl Component<Msg, NoUserEvent> for InputS3Profile {
}
}
}
// -- s3 access key
#[derive(MockComponent)]
pub struct InputS3AccessKey {
component: Input,
}
impl InputS3AccessKey {
pub fn new(access_key: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder("AKIA...", Style::default().fg(Color::Rgb(128, 128, 128)))
.title("Access key", Alignment::Left)
.input_type(InputType::Text)
.value(access_key),
}
}
}
impl Component<Msg, NoUserEvent> for InputS3AccessKey {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {
self.perform(Cmd::Move(Direction::Left));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Right, ..
}) => {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Home, ..
}) => {
self.perform(Cmd::GoTo(Position::Begin));
Some(Msg::None)
}
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
self.perform(Cmd::GoTo(Position::End));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Delete, ..
}) => {
self.perform(Cmd::Cancel);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Backspace,
..
}) => {
self.perform(Cmd::Delete);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => Some(Msg::Form(FormMsg::Connect)),
Event::Keyboard(KeyEvent {
code: Key::Down, ..
}) => Some(Msg::Ui(UiMsg::S3AccessKeyBlurDown)),
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
Some(Msg::Ui(UiMsg::S3AccessKeyBlurUp))
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
Some(Msg::Ui(UiMsg::ParamsFormBlur))
}
_ => None,
}
}
}
#[derive(MockComponent)]
pub struct InputS3SecretAccessKey {
component: Input,
}
impl InputS3SecretAccessKey {
pub fn new(secret_access_key: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.title("Secret access key", Alignment::Left)
.input_type(InputType::Password('*'))
.value(secret_access_key),
}
}
}
impl Component<Msg, NoUserEvent> for InputS3SecretAccessKey {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {
self.perform(Cmd::Move(Direction::Left));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Right, ..
}) => {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Home, ..
}) => {
self.perform(Cmd::GoTo(Position::Begin));
Some(Msg::None)
}
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
self.perform(Cmd::GoTo(Position::End));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Delete, ..
}) => {
self.perform(Cmd::Cancel);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Backspace,
..
}) => {
self.perform(Cmd::Delete);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => Some(Msg::Form(FormMsg::Connect)),
Event::Keyboard(KeyEvent {
code: Key::Down, ..
}) => Some(Msg::Ui(UiMsg::S3SecretAccessKeyBlurDown)),
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
Some(Msg::Ui(UiMsg::S3SecretAccessKeyBlurUp))
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
Some(Msg::Ui(UiMsg::ParamsFormBlur))
}
_ => None,
}
}
}
#[derive(MockComponent)]
pub struct InputS3SecurityToken {
component: Input,
}
impl InputS3SecurityToken {
pub fn new(security_token: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.title("Security token", Alignment::Left)
.input_type(InputType::Password('*'))
.value(security_token),
}
}
}
impl Component<Msg, NoUserEvent> for InputS3SecurityToken {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {
self.perform(Cmd::Move(Direction::Left));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Right, ..
}) => {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Home, ..
}) => {
self.perform(Cmd::GoTo(Position::Begin));
Some(Msg::None)
}
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
self.perform(Cmd::GoTo(Position::End));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Delete, ..
}) => {
self.perform(Cmd::Cancel);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Backspace,
..
}) => {
self.perform(Cmd::Delete);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => Some(Msg::Form(FormMsg::Connect)),
Event::Keyboard(KeyEvent {
code: Key::Down, ..
}) => Some(Msg::Ui(UiMsg::S3SecurityTokenBlurDown)),
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
Some(Msg::Ui(UiMsg::S3SecurityTokenBlurUp))
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
Some(Msg::Ui(UiMsg::ParamsFormBlur))
}
_ => None,
}
}
}
#[derive(MockComponent)]
pub struct InputS3SessionToken {
component: Input,
}
impl InputS3SessionToken {
pub fn new(session_token: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.title("Session token", Alignment::Left)
.input_type(InputType::Password('*'))
.value(session_token),
}
}
}
impl Component<Msg, NoUserEvent> for InputS3SessionToken {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {
self.perform(Cmd::Move(Direction::Left));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Right, ..
}) => {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Home, ..
}) => {
self.perform(Cmd::GoTo(Position::Begin));
Some(Msg::None)
}
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
self.perform(Cmd::GoTo(Position::End));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Delete, ..
}) => {
self.perform(Cmd::Cancel);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Backspace,
..
}) => {
self.perform(Cmd::Delete);
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => Some(Msg::Form(FormMsg::Connect)),
Event::Keyboard(KeyEvent {
code: Key::Down, ..
}) => Some(Msg::Ui(UiMsg::S3SessionTokenBlurDown)),
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
Some(Msg::Ui(UiMsg::S3SessionTokenBlurUp))
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
Some(Msg::Ui(UiMsg::ParamsFormBlur))
}
_ => None,
}
}
}

View File

@@ -37,7 +37,8 @@ pub use bookmarks::{
RecentsList,
};
pub use form::{
InputAddress, InputPassword, InputPort, InputS3Bucket, InputS3Profile, InputS3Region,
InputAddress, InputPassword, InputPort, InputS3AccessKey, InputS3Bucket, InputS3Profile,
InputS3Region, InputS3SecretAccessKey, InputS3SecurityToken, InputS3SessionToken,
InputUsername, ProtocolRadio,
};
pub use popup::{

View File

@@ -26,7 +26,7 @@
* SOFTWARE.
*/
use super::{AuthActivity, FileTransferParams, FileTransferProtocol};
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
use crate::filetransfer::params::ProtocolParams;
use crate::system::auto_update::{Release, Update, UpdateStatus};
use crate::system::notifications::Notification;
@@ -68,46 +68,32 @@ impl AuthActivity {
&self,
protocol: FileTransferProtocol,
) -> Result<FileTransferParams, &'static str> {
let (address, port, username, password): (String, u16, String, String) =
self.get_generic_params_input();
if address.is_empty() {
let params = self.get_generic_params_input();
if params.address.is_empty() {
return Err("Invalid host");
}
if port == 0 {
if params.port == 0 {
return Err("Invalid port");
}
Ok(FileTransferParams {
protocol,
params: ProtocolParams::Generic(
GenericProtocolParams::default()
.address(address)
.port(port)
.username(match username.is_empty() {
true => None,
false => Some(username),
})
.password(match password.is_empty() {
true => None,
false => Some(password),
}),
),
params: ProtocolParams::Generic(params),
entry_directory: None,
})
}
/// Get input values from fields or return an error if fields are invalid to work as aws s3
pub(super) fn collect_s3_host_params(&self) -> Result<FileTransferParams, &'static str> {
let (bucket, region, profile): (String, String, Option<String>) =
self.get_s3_params_input();
if bucket.is_empty() {
let params = self.get_s3_params_input();
if params.bucket_name.is_empty() {
return Err("Invalid bucket");
}
if region.is_empty() {
if params.region.is_empty() {
return Err("Invalid region");
}
Ok(FileTransferParams {
protocol: FileTransferProtocol::AwsS3,
params: ProtocolParams::AwsS3(AwsS3Params::new(bucket, region, profile)),
params: ProtocolParams::AwsS3(params),
entry_directory: None,
})
}

View File

@@ -66,9 +66,13 @@ pub enum Id {
Protocol,
QuitPopup,
RecentsList,
S3AccessKey,
S3Bucket,
S3Profile,
S3Region,
S3SecretAccessKey,
S3SecurityToken,
S3SessionToken,
Subtitle,
Title,
Username,
@@ -119,12 +123,20 @@ pub enum UiMsg {
ProtocolBlurDown,
ProtocolBlurUp,
RececentsListBlur,
S3AccessKeyBlurDown,
S3AccessKeyBlurUp,
S3BucketBlurDown,
S3BucketBlurUp,
S3ProfileBlurDown,
S3ProfileBlurUp,
S3RegionBlurDown,
S3RegionBlurUp,
S3SecretAccessKeyBlurDown,
S3SecretAccessKeyBlurUp,
S3SecurityTokenBlurDown,
S3SecurityTokenBlurUp,
S3SessionTokenBlurDown,
S3SessionTokenBlurUp,
BookmarkNameBlur,
SaveBookmarkPasswordBlur,
ShowDeleteBookmarkPopup,

View File

@@ -203,7 +203,7 @@ impl AuthActivity {
.app
.active(match self.input_mask() {
InputMask::Generic => &Id::Password,
InputMask::AwsS3 => &Id::S3Profile,
InputMask::AwsS3 => &Id::S3SessionToken,
})
.is_ok());
}
@@ -223,11 +223,35 @@ impl AuthActivity {
assert!(self.app.active(&Id::S3Bucket).is_ok());
}
UiMsg::S3ProfileBlurDown => {
assert!(self.app.active(&Id::Protocol).is_ok());
assert!(self.app.active(&Id::S3AccessKey).is_ok());
}
UiMsg::S3ProfileBlurUp => {
assert!(self.app.active(&Id::S3Region).is_ok());
}
UiMsg::S3AccessKeyBlurDown => {
assert!(self.app.active(&Id::S3SecretAccessKey).is_ok());
}
UiMsg::S3AccessKeyBlurUp => {
assert!(self.app.active(&Id::S3Profile).is_ok());
}
UiMsg::S3SecretAccessKeyBlurDown => {
assert!(self.app.active(&Id::S3SecurityToken).is_ok());
}
UiMsg::S3SecretAccessKeyBlurUp => {
assert!(self.app.active(&Id::S3AccessKey).is_ok());
}
UiMsg::S3SecurityTokenBlurDown => {
assert!(self.app.active(&Id::S3SessionToken).is_ok());
}
UiMsg::S3SecurityTokenBlurUp => {
assert!(self.app.active(&Id::S3SecretAccessKey).is_ok());
}
UiMsg::S3SessionTokenBlurDown => {
assert!(self.app.active(&Id::Protocol).is_ok());
}
UiMsg::S3SessionTokenBlurUp => {
assert!(self.app.active(&Id::S3SecurityToken).is_ok());
}
UiMsg::SaveBookmarkPasswordBlur => {
assert!(self.app.active(&Id::BookmarkName).is_ok());
}

View File

@@ -27,7 +27,7 @@
*/
// Locals
use super::{components, AuthActivity, Context, FileTransferProtocol, Id, InputMask};
use crate::filetransfer::params::ProtocolParams;
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
use crate::filetransfer::FileTransferParams;
use crate::utils::ui::draw_area_in;
@@ -74,6 +74,10 @@ impl AuthActivity {
self.mount_s3_bucket("");
self.mount_s3_profile("");
self.mount_s3_region("");
self.mount_s3_access_key("");
self.mount_s3_secret_access_key("");
self.mount_s3_security_token("");
self.mount_s3_session_token("");
// Version notice
if let Some(version) = self
.context()
@@ -158,6 +162,7 @@ impl AuthActivity {
Constraint::Length(3), // bucket
Constraint::Length(3), // region
Constraint::Length(3), // profile
Constraint::Length(3), // access_key
]
.as_ref(),
)
@@ -190,9 +195,11 @@ impl AuthActivity {
// Render input mask
match self.input_mask() {
InputMask::AwsS3 => {
self.app.view(&Id::S3Bucket, f, input_mask[0]);
self.app.view(&Id::S3Region, f, input_mask[1]);
self.app.view(&Id::S3Profile, f, input_mask[2]);
let s3_view_ids = self.get_s3_view();
self.app.view(&s3_view_ids[0], f, input_mask[0]);
self.app.view(&s3_view_ids[1], f, input_mask[1]);
self.app.view(&s3_view_ids[2], f, input_mask[2]);
self.app.view(&s3_view_ids[3], f, input_mask[3]);
}
InputMask::Generic => {
self.app.view(&Id::Address, f, input_mask[0]);
@@ -653,23 +660,83 @@ impl AuthActivity {
.is_ok());
}
pub(crate) fn mount_s3_access_key(&mut self, key: &str) {
let password_color = self.theme().auth_password;
assert!(self
.app
.remount(
Id::S3AccessKey,
Box::new(components::InputS3AccessKey::new(key, password_color)),
vec![]
)
.is_ok());
}
pub(crate) fn mount_s3_secret_access_key(&mut self, key: &str) {
let addr_color = self.theme().auth_address;
assert!(self
.app
.remount(
Id::S3SecretAccessKey,
Box::new(components::InputS3SecretAccessKey::new(key, addr_color)),
vec![]
)
.is_ok());
}
pub(crate) fn mount_s3_security_token(&mut self, token: &str) {
let port_color = self.theme().auth_port;
assert!(self
.app
.remount(
Id::S3SecurityToken,
Box::new(components::InputS3SecurityToken::new(token, port_color)),
vec![]
)
.is_ok());
}
pub(crate) fn mount_s3_session_token(&mut self, token: &str) {
let username_color = self.theme().auth_username;
assert!(self
.app
.remount(
Id::S3SessionToken,
Box::new(components::InputS3SessionToken::new(token, username_color)),
vec![]
)
.is_ok());
}
// -- query
/// Collect input values from view
pub(super) fn get_generic_params_input(&self) -> (String, u16, String, String) {
pub(super) fn get_generic_params_input(&self) -> GenericProtocolParams {
let addr: String = self.get_input_addr();
let port: u16 = self.get_input_port();
let username: String = self.get_input_username();
let password: String = self.get_input_password();
(addr, port, username, password)
let username = self.get_input_username();
let password = self.get_input_password();
GenericProtocolParams::default()
.address(addr)
.port(port)
.username(username)
.password(password)
}
/// Collect s3 input values from view
pub(super) fn get_s3_params_input(&self) -> (String, String, Option<String>) {
pub(super) fn get_s3_params_input(&self) -> AwsS3Params {
let bucket: String = self.get_input_s3_bucket();
let region: String = self.get_input_s3_region();
let profile: Option<String> = self.get_input_s3_profile();
(bucket, region, profile)
let access_key = self.get_input_s3_access_key();
let secret_access_key = self.get_input_s3_secret_access_key();
let security_token = self.get_input_s3_security_token();
let session_token = self.get_input_s3_session_token();
AwsS3Params::new(bucket, region, profile)
.access_key(access_key)
.secret_access_key(secret_access_key)
.security_token(security_token)
.session_token(session_token)
}
pub(super) fn get_input_addr(&self) -> String {
@@ -689,17 +756,17 @@ impl AuthActivity {
}
}
pub(super) fn get_input_username(&self) -> String {
pub(super) fn get_input_username(&self) -> Option<String> {
match self.app.state(&Id::Username) {
Ok(State::One(StateValue::String(x))) => x,
_ => String::new(),
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_password(&self) -> String {
pub(super) fn get_input_password(&self) -> Option<String> {
match self.app.state(&Id::Password) {
Ok(State::One(StateValue::String(x))) => x,
_ => String::new(),
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
@@ -724,6 +791,34 @@ impl AuthActivity {
}
}
pub(super) fn get_input_s3_access_key(&self) -> Option<String> {
match self.app.state(&Id::S3AccessKey) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_s3_secret_access_key(&self) -> Option<String> {
match self.app.state(&Id::S3SecretAccessKey) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_s3_security_token(&self) -> Option<String> {
match self.app.state(&Id::S3SecurityToken) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_s3_session_token(&self) -> Option<String> {
match self.app.state(&Id::S3SessionToken) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
/// Get new bookmark params
pub(super) fn get_new_bookmark(&self) -> (String, bool) {
let name = match self.app.state(&Id::BookmarkName) {
@@ -745,7 +840,7 @@ impl AuthActivity {
/// Returns the input mask size based on current input mask
pub(super) fn input_mask_size(&self) -> u16 {
match self.input_mask() {
InputMask::AwsS3 => 9,
InputMask::AwsS3 => 12,
InputMask::Generic => 12,
}
}
@@ -785,6 +880,19 @@ impl AuthActivity {
}
}
/// Get the visible element in the aws-s3 form, based on current focus
fn get_s3_view(&self) -> [Id; 4] {
match self.app.focus() {
Some(&Id::S3SecretAccessKey | &Id::S3SecurityToken | &Id::S3SessionToken) => [
Id::S3AccessKey,
Id::S3SecretAccessKey,
Id::S3SecurityToken,
Id::S3SessionToken,
],
_ => [Id::S3Bucket, Id::S3Region, Id::S3Profile, Id::S3AccessKey],
}
}
fn init_global_listener(&mut self) {
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
assert!(self

View File

@@ -240,7 +240,7 @@ impl Component<Msg, NoUserEvent> for SshHost {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)
@@ -319,7 +319,7 @@ impl Component<Msg, NoUserEvent> for SshUsername {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
self.perform(Cmd::Type(ch));
Some(Msg::None)

View File

@@ -895,7 +895,7 @@ impl Component<Msg, NoUserEvent> for InputColor {
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
let result = self.perform(Cmd::Type(ch));
self.update_color(result)