mirror of
https://github.com/veeso/termscp.git
synced 2025-12-06 17:15:35 -08:00
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:
@@ -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
6
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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` 文件中的凭据。 下面看看如何配置环境:
|
||||
|
||||
这些应该始终是强制性的:
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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: ")) {
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ impl BookmarkSavePassword {
|
||||
.value(0)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Save password?", Alignment::Center),
|
||||
.title("Save secrets?", Alignment::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user