mirror of
https://github.com/veeso/termscp.git
synced 2026-09-26 22:11:26 -07:00
Deploy docs to GitHub Pages / deploy (push) Has been cancelled
Site / build-site (push) Has been cancelled
Install.sh / build (macos-latest) (push) Has been cancelled
Install.sh / build (ubuntu-latest) (push) Has been cancelled
CI / toolchain (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / install-scripts (push) Has been cancelled
CI / crates-ubuntu-latest (push) Has been cancelled
CI / crates-windows-latest (push) Has been cancelled
CI / doc (push) Has been cancelled
CI / deny (push) Has been cancelled
CI / crates-macos-latest (push) Has been cancelled
* ci: migrate project automation to Just Centralize build, test, release, dependency, hook, and website commands in Just recipes. Pin workflow tooling, use the repository toolchain, and run the complete validation set in CI. * ci: codex being codex * docs: update CLAUDE.md for just task runner migration Reflect the switch to just recipes for build/test/clippy/fmt, note dprint replacing raw rustfmt, and add a cross-platform code requirement. * fix: resolve clippy warnings breaking CI on ubuntu and windows Use clone() instead of implicit to_string() on already-owned String values, gate the windows-only unused make_file_at import behind cfg(posix), and fix unused mut / manual assign-op in the windows-only localhost test. * fix: fmt
755 lines
27 KiB
Rust
755 lines
27 KiB
Rust
//! ## ConfigClient
|
|
//!
|
|
//! `config_client` is the module which provides an API between the Config module and the system
|
|
|
|
// Locals
|
|
// Ext
|
|
use std::fs::{File, OpenOptions, create_dir, remove_file};
|
|
use std::io::Write;
|
|
use std::path::{Path, PathBuf};
|
|
use std::str::FromStr;
|
|
use std::string::ToString;
|
|
|
|
use crate::config::params::{DEFAULT_NOTIFICATION_TRANSFER_THRESHOLD, UserConfig};
|
|
use crate::config::serialization::{SerializerError, SerializerErrorKind, deserialize, serialize};
|
|
use crate::explorer::GroupDirs;
|
|
use crate::filetransfer::FileTransferProtocol;
|
|
|
|
// Types
|
|
pub type SshHost = (String, String, PathBuf); // 0: host, 1: username, 2: RSA key path
|
|
|
|
/// ConfigClient provides a high level API to communicate with the termscp configuration
|
|
pub struct ConfigClient {
|
|
config: UserConfig, // Configuration loaded
|
|
config_path: PathBuf, // Configuration TOML Path
|
|
ssh_key_dir: PathBuf, // SSH Key storage directory
|
|
degraded: bool, // Indicates the `ConfigClient` is working in degraded mode
|
|
}
|
|
|
|
impl ConfigClient {
|
|
/// Instantiate a new `ConfigClient` with provided path
|
|
pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result<Self, SerializerError> {
|
|
// Initialize a default configuration
|
|
let default_config: UserConfig = UserConfig::default();
|
|
info!(
|
|
"Setting up config client with config path {} and SSH key directory {}",
|
|
config_path.display(),
|
|
ssh_key_dir.display()
|
|
);
|
|
// Create client
|
|
let mut client: ConfigClient = ConfigClient {
|
|
config: default_config,
|
|
config_path: PathBuf::from(config_path),
|
|
ssh_key_dir: PathBuf::from(ssh_key_dir),
|
|
degraded: false,
|
|
};
|
|
// If ssh key directory doesn't exist, create it
|
|
if !ssh_key_dir.exists() {
|
|
if let Err(err) = create_dir(ssh_key_dir) {
|
|
error!("Failed to create SSH key dir: {}", err);
|
|
return Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Io,
|
|
format!(
|
|
"Could not create SSH key directory \"{}\": {}",
|
|
ssh_key_dir.display(),
|
|
err
|
|
),
|
|
));
|
|
}
|
|
debug!("Created SSH key directory");
|
|
}
|
|
// If Config file doesn't exist, create it
|
|
if !config_path.exists() {
|
|
if let Err(err) = client.write_config() {
|
|
error!("Couldn't create configuration file: {}", err);
|
|
return Err(err);
|
|
}
|
|
debug!("Config file didn't exist; created file");
|
|
} else {
|
|
// otherwise Load configuration from file
|
|
if let Err(err) = client.read_config() {
|
|
error!("Couldn't read configuration file: {}", err);
|
|
return Err(err);
|
|
}
|
|
debug!("Read configuration file");
|
|
}
|
|
Ok(client)
|
|
}
|
|
|
|
/// Instantiate a ConfigClient in degraded mode.
|
|
/// When in degraded mode, the configuration in use will be the default configuration
|
|
/// and the IO operation on configuration won't be available
|
|
pub fn degraded() -> Self {
|
|
Self {
|
|
config: UserConfig::default(),
|
|
config_path: PathBuf::default(),
|
|
ssh_key_dir: PathBuf::default(),
|
|
degraded: true,
|
|
}
|
|
}
|
|
|
|
// Text editor
|
|
|
|
/// Get text editor from configuration
|
|
pub fn get_text_editor(&self) -> PathBuf {
|
|
self.config.user_interface.text_editor.clone()
|
|
}
|
|
|
|
/// Set text editor path
|
|
pub fn set_text_editor(&mut self, path: PathBuf) {
|
|
self.config.user_interface.text_editor = path;
|
|
}
|
|
|
|
// Default protocol
|
|
|
|
/// Get default protocol from configuration
|
|
pub fn get_default_protocol(&self) -> FileTransferProtocol {
|
|
match FileTransferProtocol::from_str(self.config.user_interface.default_protocol.as_str()) {
|
|
Ok(p) => p,
|
|
Err(_) => FileTransferProtocol::Sftp,
|
|
}
|
|
}
|
|
|
|
/// Set default protocol to configuration
|
|
pub fn set_default_protocol(&mut self, proto: FileTransferProtocol) {
|
|
self.config.user_interface.default_protocol = proto.to_string();
|
|
}
|
|
|
|
/// Get value of `show_hidden_files`
|
|
pub fn get_show_hidden_files(&self) -> bool {
|
|
self.config.user_interface.show_hidden_files
|
|
}
|
|
|
|
/// Set new value for `show_hidden_files`
|
|
pub fn set_show_hidden_files(&mut self, value: bool) {
|
|
self.config.user_interface.show_hidden_files = value;
|
|
}
|
|
|
|
/// Get value of `check_for_updates`
|
|
pub fn get_check_for_updates(&self) -> bool {
|
|
self.config.user_interface.check_for_updates.unwrap_or(true)
|
|
}
|
|
|
|
/// Set new value for `check_for_updates`
|
|
pub fn set_check_for_updates(&mut self, value: bool) {
|
|
self.config.user_interface.check_for_updates = Some(value);
|
|
}
|
|
|
|
/// Get value of `prompt_on_file_replace`
|
|
pub fn get_prompt_on_file_replace(&self) -> bool {
|
|
self.config
|
|
.user_interface
|
|
.prompt_on_file_replace
|
|
.unwrap_or(true)
|
|
}
|
|
|
|
/// Set new value for `prompt_on_file_replace`
|
|
pub fn set_prompt_on_file_replace(&mut self, value: bool) {
|
|
self.config.user_interface.prompt_on_file_replace = Some(value);
|
|
}
|
|
|
|
/// Get GroupDirs value from configuration (will be converted from string)
|
|
pub fn get_group_dirs(&self) -> Option<GroupDirs> {
|
|
// Convert string to `GroupDirs`
|
|
match &self.config.user_interface.group_dirs {
|
|
None => None,
|
|
Some(val) => GroupDirs::from_str(val.as_str()).ok(),
|
|
}
|
|
}
|
|
|
|
/// Set value for group_dir in configuration.
|
|
/// Provided value, if `Some` will be converted to `GroupDirs`
|
|
pub fn set_group_dirs(&mut self, val: Option<GroupDirs>) {
|
|
self.config.user_interface.group_dirs = val.map(|val| val.to_string());
|
|
}
|
|
|
|
/// Get current file fmt for local host
|
|
pub fn get_local_file_fmt(&self) -> Option<String> {
|
|
self.config.user_interface.file_fmt.clone()
|
|
}
|
|
|
|
/// Set file fmt parameter for local host
|
|
pub fn set_local_file_fmt(&mut self, s: String) {
|
|
self.config.user_interface.file_fmt = match s.is_empty() {
|
|
true => None,
|
|
false => Some(s),
|
|
};
|
|
}
|
|
|
|
/// Get current file fmt for remote host
|
|
pub fn get_remote_file_fmt(&self) -> Option<String> {
|
|
self.config.user_interface.remote_file_fmt.clone()
|
|
}
|
|
|
|
/// Set file fmt parameter for remote host
|
|
pub fn set_remote_file_fmt(&mut self, s: String) {
|
|
self.config.user_interface.remote_file_fmt = match s.is_empty() {
|
|
true => None,
|
|
false => Some(s),
|
|
};
|
|
}
|
|
|
|
/// Get value of `notifications`
|
|
pub fn get_notifications(&self) -> bool {
|
|
self.config.user_interface.notifications.unwrap_or(true)
|
|
}
|
|
|
|
/// Set new value for `notifications`
|
|
pub fn set_notifications(&mut self, value: bool) {
|
|
self.config.user_interface.notifications = Some(value);
|
|
}
|
|
|
|
/// Get value of `notification_threshold`
|
|
pub fn get_notification_threshold(&self) -> u64 {
|
|
self.config
|
|
.user_interface
|
|
.notification_threshold
|
|
.unwrap_or(DEFAULT_NOTIFICATION_TRANSFER_THRESHOLD)
|
|
}
|
|
|
|
/// Set new value for `notification_threshold`
|
|
pub fn set_notification_threshold(&mut self, value: u64) {
|
|
self.config.user_interface.notification_threshold = Some(value);
|
|
}
|
|
|
|
// Remote params
|
|
|
|
/// Get ssh config path
|
|
pub fn get_ssh_config(&self) -> Option<&str> {
|
|
self.config.remote.ssh_config.as_deref()
|
|
}
|
|
|
|
/// Set ssh config path
|
|
pub fn set_ssh_config(&mut self, p: Option<String>) {
|
|
self.config.remote.ssh_config = p;
|
|
}
|
|
|
|
// SSH Keys
|
|
|
|
/// Save a SSH key into configuration.
|
|
/// This operation also creates the key file in `ssh_key_dir`
|
|
/// and also commits changes to configuration, to prevent incoerent data
|
|
pub fn add_ssh_key(
|
|
&mut self,
|
|
host: &str,
|
|
username: &str,
|
|
ssh_key: &str,
|
|
) -> Result<(), SerializerError> {
|
|
if self.degraded {
|
|
return Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Generic,
|
|
String::from("Configuration won't be saved, since in degraded mode"),
|
|
));
|
|
}
|
|
let host_name: String = Self::make_ssh_host_key(host, username);
|
|
// Get key path
|
|
let ssh_key_path: PathBuf = {
|
|
let mut p: PathBuf = self.ssh_key_dir.clone();
|
|
p.push(format!("{host_name}.key"));
|
|
p
|
|
};
|
|
info!(
|
|
"Writing SSH file to {} for host {}",
|
|
ssh_key_path.display(),
|
|
host_name
|
|
);
|
|
// Write key to file
|
|
let mut f: File = match File::create(ssh_key_path.as_path()) {
|
|
Ok(f) => f,
|
|
Err(err) => return Self::make_io_err(err),
|
|
};
|
|
if let Err(err) = f.write_all(ssh_key.as_bytes()) {
|
|
error!("Failed to write SSH key to file: {}", err);
|
|
return Self::make_io_err(err);
|
|
}
|
|
// Add host to keys
|
|
self.config.remote.ssh_keys.insert(host_name, ssh_key_path);
|
|
// Write config
|
|
self.write_config()
|
|
}
|
|
|
|
/// Delete a ssh key from configuration, using host as key.
|
|
/// This operation also unlinks the key file in `ssh_key_dir`
|
|
/// and also commits changes to configuration, to prevent incoerent data
|
|
pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> {
|
|
if self.degraded {
|
|
return Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Generic,
|
|
String::from("Configuration won't be saved, since in degraded mode"),
|
|
));
|
|
}
|
|
// Remove key from configuration and get key path
|
|
info!("Removing key for {}@{}", host, username);
|
|
let key_path: PathBuf = match self
|
|
.config
|
|
.remote
|
|
.ssh_keys
|
|
.remove(&Self::make_ssh_host_key(host, username))
|
|
{
|
|
Some(p) => p,
|
|
None => return Ok(()), // Return ok if host doesn't exist
|
|
};
|
|
// Remove file
|
|
if let Err(err) = remove_file(key_path.as_path()) {
|
|
error!("Failed to remove key file {}: {}", key_path.display(), err);
|
|
return Self::make_io_err(err);
|
|
}
|
|
// Commit changes to configuration
|
|
self.write_config()
|
|
}
|
|
|
|
/// Get ssh key from host.
|
|
/// None is returned if key doesn't exist
|
|
pub fn get_ssh_key(&self, mkey: &str) -> Option<SshHost> {
|
|
if self.degraded {
|
|
return None;
|
|
}
|
|
// Check if Key exists
|
|
match self.config.remote.ssh_keys.get(mkey) {
|
|
None => None,
|
|
Some(key_path) => {
|
|
// Get host and username
|
|
let (host, username) = Self::get_ssh_tokens(mkey)?;
|
|
// Return key
|
|
Some((host, username, PathBuf::from(key_path)))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get an iterator through hosts in the ssh key storage
|
|
pub fn iter_ssh_keys(&self) -> impl Iterator<Item = &String> + '_ {
|
|
Box::new(self.config.remote.ssh_keys.keys())
|
|
}
|
|
|
|
// I/O
|
|
|
|
/// Write configuration to file
|
|
pub fn write_config(&self) -> Result<(), SerializerError> {
|
|
if self.degraded {
|
|
return Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Generic,
|
|
String::from("Configuration won't be saved, since in degraded mode"),
|
|
));
|
|
}
|
|
// Open file
|
|
match OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.truncate(true)
|
|
.open(self.config_path.as_path())
|
|
{
|
|
Ok(writer) => serialize(&self.config, Box::new(writer)),
|
|
Err(err) => {
|
|
error!("Failed to write configuration file: {}", err);
|
|
Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Io,
|
|
err.to_string(),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Read configuration from file (or reload it if already read)
|
|
pub fn read_config(&mut self) -> Result<(), SerializerError> {
|
|
if self.degraded {
|
|
return Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Generic,
|
|
String::from("Configuration won't be loaded, since in degraded mode"),
|
|
));
|
|
}
|
|
// Open bookmarks file for read
|
|
match OpenOptions::new()
|
|
.read(true)
|
|
.open(self.config_path.as_path())
|
|
{
|
|
Ok(reader) => {
|
|
// Deserialize
|
|
match deserialize(Box::new(reader)) {
|
|
Ok(config) => {
|
|
self.config = config;
|
|
Ok(())
|
|
}
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
Err(err) => {
|
|
error!("Failed to read configuration: {}", err);
|
|
Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Io,
|
|
err.to_string(),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Hosts are saved as `username@host` into configuration.
|
|
/// This method creates the key name, starting from host and username
|
|
fn make_ssh_host_key(host: &str, username: &str) -> String {
|
|
format!("{username}@{host}")
|
|
}
|
|
|
|
/// Get ssh tokens starting from ssh host key
|
|
/// Returns: (host, username) or None if key has invalid syntax
|
|
fn get_ssh_tokens(host_key: &str) -> Option<(String, String)> {
|
|
let tokens: Vec<&str> = host_key.split('@').collect();
|
|
if tokens.len() >= 2 {
|
|
Some((String::from(tokens[1]), String::from(tokens[0])))
|
|
} else {
|
|
error!(
|
|
"Invalid SSH host key format: '{}' (expected 'username@host')",
|
|
host_key
|
|
);
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Make serializer error from `std::io::Error`
|
|
fn make_io_err(err: std::io::Error) -> Result<(), SerializerError> {
|
|
Err(SerializerError::new_ex(
|
|
SerializerErrorKind::Io,
|
|
err.to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use std::io::Read;
|
|
|
|
use pretty_assertions::assert_eq;
|
|
use tempfile::TempDir;
|
|
|
|
use super::*;
|
|
use crate::config::params::UserConfig;
|
|
use crate::utils::random::random_alphanumeric_with_len;
|
|
|
|
#[test]
|
|
fn test_system_config_new() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, ssh_keys_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), ssh_keys_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
// Verify parameters
|
|
let default_config: UserConfig = UserConfig::default();
|
|
assert_eq!(client.degraded, false);
|
|
assert_eq!(client.config.remote.ssh_keys.len(), 0);
|
|
assert_eq!(
|
|
client.config.user_interface.default_protocol,
|
|
default_config.user_interface.default_protocol
|
|
);
|
|
assert_eq!(
|
|
client.config.user_interface.text_editor,
|
|
default_config.user_interface.text_editor
|
|
);
|
|
assert_eq!(client.config_path, cfg_path);
|
|
assert_eq!(client.ssh_key_dir, ssh_keys_path);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_degraded() {
|
|
let mut client: ConfigClient = ConfigClient::degraded();
|
|
assert_eq!(client.degraded, true);
|
|
assert_eq!(client.config_path, PathBuf::default());
|
|
assert_eq!(client.ssh_key_dir, PathBuf::default());
|
|
// I/O
|
|
assert!(client.add_ssh_key("Omar", "omar", "omar").is_err());
|
|
assert!(client.del_ssh_key("omar", "omar").is_err());
|
|
assert!(client.get_ssh_key("omar").is_none());
|
|
assert!(client.write_config().is_err());
|
|
assert!(client.read_config().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_new_err() {
|
|
assert!(
|
|
ConfigClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar"),)
|
|
.is_err()
|
|
);
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
assert!(ConfigClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar")).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_from_existing() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
// Change some stuff
|
|
client.set_text_editor(PathBuf::from("/usr/bin/vim"));
|
|
client.set_default_protocol(FileTransferProtocol::Scp);
|
|
assert!(
|
|
client
|
|
.add_ssh_key("192.168.1.31", "pi", "piroporopero")
|
|
.is_ok()
|
|
);
|
|
assert!(client.write_config().is_ok());
|
|
// Istantiate a new client
|
|
let client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
// Verify client has updated parameters
|
|
assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
|
|
assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim"));
|
|
let mut expected_key_path: PathBuf = key_path;
|
|
expected_key_path.push("pi@192.168.1.31.key");
|
|
assert_eq!(
|
|
client.get_ssh_key("pi@192.168.1.31").unwrap(),
|
|
(
|
|
String::from("192.168.1.31"),
|
|
String::from("pi"),
|
|
expected_key_path,
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_text_editor() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
client.set_text_editor(PathBuf::from("mcedit"));
|
|
assert_eq!(client.get_text_editor(), PathBuf::from("mcedit"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_default_protocol() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
client.set_default_protocol(FileTransferProtocol::Ftp(true));
|
|
assert_eq!(
|
|
client.get_default_protocol(),
|
|
FileTransferProtocol::Ftp(true)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_show_hidden_files() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
client.set_show_hidden_files(true);
|
|
assert_eq!(client.get_show_hidden_files(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_check_for_updates() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
assert_eq!(client.get_check_for_updates(), true); // Null ?
|
|
client.set_check_for_updates(true);
|
|
assert_eq!(client.get_check_for_updates(), true);
|
|
client.set_check_for_updates(false);
|
|
assert_eq!(client.get_check_for_updates(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_prompt_on_file_replace() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
assert_eq!(client.get_prompt_on_file_replace(), true); // Null ?
|
|
client.set_prompt_on_file_replace(true);
|
|
assert_eq!(client.get_prompt_on_file_replace(), true);
|
|
client.set_prompt_on_file_replace(false);
|
|
assert_eq!(client.get_prompt_on_file_replace(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_group_dirs() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
client.set_group_dirs(Some(GroupDirs::First));
|
|
assert_eq!(client.get_group_dirs(), Some(GroupDirs::First),);
|
|
client.set_group_dirs(None);
|
|
assert_eq!(client.get_group_dirs(), None,);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_local_file_fmt() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
assert_eq!(client.get_local_file_fmt(), None);
|
|
client.set_local_file_fmt(String::from("{NAME}"));
|
|
assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}"));
|
|
// Delete
|
|
client.set_local_file_fmt(String::new());
|
|
assert_eq!(client.get_local_file_fmt(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_remote_file_fmt() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
assert_eq!(client.get_remote_file_fmt(), None);
|
|
client.set_remote_file_fmt(String::from("{NAME}"));
|
|
assert_eq!(
|
|
client.get_remote_file_fmt().unwrap(),
|
|
String::from("{NAME}")
|
|
);
|
|
// Delete
|
|
client.set_remote_file_fmt(String::new());
|
|
assert_eq!(client.get_remote_file_fmt(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_notifications() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
assert_eq!(client.get_notifications(), true); // Null ?
|
|
client.set_notifications(true);
|
|
assert_eq!(client.get_notifications(), true);
|
|
client.set_notifications(false);
|
|
assert_eq!(client.get_notifications(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_remote_notification_threshold() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
assert_eq!(
|
|
client.get_notification_threshold(),
|
|
DEFAULT_NOTIFICATION_TRANSFER_THRESHOLD
|
|
); // Null ?
|
|
client.set_notification_threshold(1024);
|
|
assert_eq!(client.get_notification_threshold(), 1024);
|
|
client.set_notification_threshold(64);
|
|
assert_eq!(client.get_notification_threshold(), 64);
|
|
}
|
|
|
|
#[test]
|
|
fn should_get_and_set_ssh_config_dir() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
|
|
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/root"));
|
|
let mut ssh_config_path = "~/.ssh/config".to_string();
|
|
ssh_config_path = ssh_config_path.replacen('~', &home_dir.to_string_lossy(), 1);
|
|
|
|
let ssh_config_path = if PathBuf::from(&ssh_config_path).exists() {
|
|
Some(ssh_config_path)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
assert_eq!(client.get_ssh_config(), ssh_config_path.as_deref()); // Null ?
|
|
client.set_ssh_config(Some(String::from("/tmp/ssh_config")));
|
|
assert_eq!(client.get_ssh_config(), Some("/tmp/ssh_config"));
|
|
client.set_ssh_config(None);
|
|
assert_eq!(client.get_ssh_config(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_ssh_keys() {
|
|
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
|
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
|
.ok()
|
|
.unwrap();
|
|
// Add a new key
|
|
let rsa_key: String = get_sample_rsa_key();
|
|
assert!(
|
|
client
|
|
.add_ssh_key("192.168.1.31", "pi", rsa_key.as_str())
|
|
.is_ok()
|
|
);
|
|
// Iterate keys
|
|
for key in client.iter_ssh_keys() {
|
|
let host: SshHost = client.get_ssh_key(key).unwrap();
|
|
assert_eq!(host.0, String::from("192.168.1.31"));
|
|
assert_eq!(host.1, String::from("pi"));
|
|
let mut expected_key_path: PathBuf = key_path.clone();
|
|
expected_key_path.push("pi@192.168.1.31.key");
|
|
assert_eq!(host.2, expected_key_path);
|
|
// Read rsa key
|
|
let mut key_file: File = File::open(expected_key_path.as_path()).ok().unwrap();
|
|
// Read
|
|
let mut key: String = String::new();
|
|
assert!(key_file.read_to_string(&mut key).is_ok());
|
|
// Verify rsa key
|
|
assert_eq!(key, rsa_key);
|
|
}
|
|
// Unexisting key
|
|
assert!(client.get_ssh_key("test").is_none());
|
|
// Delete key
|
|
assert!(client.del_ssh_key("192.168.1.31", "pi").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_make_key() {
|
|
assert_eq!(
|
|
ConfigClient::make_ssh_host_key("192.168.1.31", "pi"),
|
|
String::from("pi@192.168.1.31")
|
|
);
|
|
assert_eq!(
|
|
ConfigClient::get_ssh_tokens("pi@192.168.1.31"),
|
|
Some((String::from("192.168.1.31"), String::from("pi")))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_get_ssh_tokens_invalid() {
|
|
assert!(ConfigClient::get_ssh_tokens("invalid").is_none());
|
|
assert!(ConfigClient::get_ssh_tokens("").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_config_make_io_err() {
|
|
let err: SerializerError =
|
|
ConfigClient::make_io_err(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
|
|
.err()
|
|
.unwrap();
|
|
assert_eq!(err.to_string(), "IO error (permission denied)");
|
|
}
|
|
|
|
/// Get paths for configuration and keys directory
|
|
fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
|
|
let mut k: PathBuf = PathBuf::from(dir);
|
|
let mut c: PathBuf = k.clone();
|
|
k.push("ssh-keys/");
|
|
c.push("config.toml");
|
|
(c, k)
|
|
}
|
|
|
|
fn get_sample_rsa_key() -> String {
|
|
format!(
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n{}\n-----END OPENSSH PRIVATE KEY-----",
|
|
random_alphanumeric_with_len(2536)
|
|
)
|
|
}
|
|
}
|