• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / termscp / 4730861037

pending completion
4730861037

Pull #164

github

GitHub
Merge 1dce36354 into efb30231e
Pull Request #164: bump remotefs-ssh to 0.1.5

5010 of 5343 relevant lines covered (93.77%)

15.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.82
/src/system/bookmarks_client.rs
1
//! ## BookmarksClient
×
2
//!
3
//! `bookmarks_client` is the module which provides an API between the Bookmarks module and the system
4

5
// Crate
6
#[cfg(feature = "with-keyring")]
7
use super::keys::keyringstorage::KeyringStorage;
8
use super::keys::{filestorage::FileStorage, KeyStorage, KeyStorageError};
9
// Local
10
use crate::config::{
11
    bookmarks::{Bookmark, UserHosts},
12
    serialization::{deserialize, serialize, SerializerError, SerializerErrorKind},
13
};
14
use crate::filetransfer::FileTransferParams;
15
use crate::utils::crypto;
16
use crate::utils::fmt::fmt_time;
17
use crate::utils::random::random_alphanumeric_with_len;
18
// Ext
19
use std::fs::OpenOptions;
20
use std::path::{Path, PathBuf};
21
use std::string::ToString;
22
use std::time::SystemTime;
23

24
/// BookmarksClient provides a layer between the host system and the bookmarks module
25
pub struct BookmarksClient {
26
    hosts: UserHosts,
27
    bookmarks_file: PathBuf,
28
    key: String,
29
    recents_size: usize,
30
}
31

32
impl BookmarksClient {
33
    /// Instantiates a new BookmarksClient
34
    /// Bookmarks file path must be provided
35
    /// Storage path for file provider must be provided
36
    pub fn new(
16✔
37
        bookmarks_file: &Path,
38
        storage_path: &Path,
39
        recents_size: usize,
40
    ) -> Result<BookmarksClient, SerializerError> {
41
        // Create default hosts
42
        let default_hosts: UserHosts = UserHosts::default();
16✔
43
        debug!("Setting up bookmarks client...");
16✔
44
        // Make a key storage (with-keyring)
45
        #[cfg(feature = "with-keyring")]
46
        let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
47
            debug!("Setting up KeyStorage");
48
            let username: String = whoami::username();
49
            let storage: KeyringStorage = KeyringStorage::new(username.as_str());
50
            // Check if keyring storage is supported
51
            #[cfg(not(test))]
52
            let app_name: &str = "termscp";
53
            #[cfg(test)] // NOTE: when running test, add -test
54
            let app_name: &str = "termscp-test";
55
            match storage.is_supported() {
56
                true => {
57
                    debug!("Using KeyringStorage");
58
                    (Box::new(storage), app_name)
59
                }
60
                false => {
61
                    warn!("KeyringStorage is not supported; using FileStorage");
62
                    (Box::new(FileStorage::new(storage_path)), "bookmarks")
63
                }
64
            }
65
        };
66
        // Make a key storage (wno-keyring)
67
        #[cfg(not(feature = "with-keyring"))]
68
        let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
16✔
69
            #[cfg(not(test))]
70
            let app_name: &str = "bookmarks";
71
            #[cfg(test)] // NOTE: when running test, add -test
72
            let app_name: &str = "bookmarks-test";
16✔
73
            debug!("Using FileStorage");
16✔
74
            (Box::new(FileStorage::new(storage_path)), app_name)
16✔
75
        };
76
        // Load key
77
        let key: String = match key_storage.get_key(service_id) {
16✔
78
            Ok(k) => {
1✔
79
                debug!("Key loaded with success");
1✔
80
                k
1✔
81
            }
1✔
82
            Err(e) => match e {
15✔
83
                KeyStorageError::NoSuchKey => {
84
                    // If no such key, generate key and set it into the storage
85
                    let key: String = Self::generate_key();
15✔
86
                    debug!("Key doesn't exist yet or could not be loaded; generated a new key");
15✔
87
                    if let Err(e) = key_storage.set_key(service_id, key.as_str()) {
15✔
88
                        error!("Failed to set new key into storage: {}", e);
2✔
89
                        return Err(SerializerError::new_ex(
2✔
90
                            SerializerErrorKind::Io,
2✔
91
                            format!("Could not write key to storage: {e}"),
2✔
92
                        ));
93
                    }
94
                    // Return key
95
                    key
13✔
96
                }
2✔
97
                _ => {
98
                    error!("Failed to get key from storage: {}", e);
×
99
                    return Err(SerializerError::new_ex(
×
100
                        SerializerErrorKind::Io,
×
101
                        format!("Could not get key from storage: {e}"),
×
102
                    ));
103
                }
104
            },
13✔
105
        };
106
        let mut client: BookmarksClient = BookmarksClient {
14✔
107
            hosts: default_hosts,
14✔
108
            bookmarks_file: PathBuf::from(bookmarks_file),
14✔
109
            key,
14✔
110
            recents_size,
111
        };
112
        // If bookmark file doesn't exist, initialize it
113
        if !bookmarks_file.exists() {
14✔
114
            info!("Bookmarks file doesn't exist yet; creating it...");
13✔
115
            if let Err(err) = client.write_bookmarks() {
13✔
116
                error!("Failed to create bookmarks file: {}", err);
×
117
                return Err(err);
×
118
            }
119
        } else {
13✔
120
            // Load bookmarks from file
121
            if let Err(err) = client.read_bookmarks() {
1✔
122
                error!("Failed to load bookmarks: {}", err);
×
123
                return Err(err);
×
124
            }
125
        }
1✔
126
        info!("Bookmarks client initialized");
14✔
127
        // Load key
128
        Ok(client)
14✔
129
    }
16✔
130

131
    /// Iterate over bookmarks keys
132
    pub fn iter_bookmarks(&self) -> impl Iterator<Item = &String> + '_ {
1✔
133
        Box::new(self.hosts.bookmarks.keys())
1✔
134
    }
1✔
135

136
    /// Get bookmark associated to key
137
    pub fn get_bookmark(&self, key: &str) -> Option<FileTransferParams> {
7✔
138
        debug!("Getting bookmark {}", key);
7✔
139
        let mut entry: Bookmark = self.hosts.bookmarks.get(key).cloned()?;
7✔
140
        // Decrypt password first
141
        if let Some(pwd) = entry.password.as_mut() {
5✔
142
            match self.decrypt_str(pwd.as_str()) {
2✔
143
                Ok(decrypted_pwd) => {
2✔
144
                    *pwd = decrypted_pwd;
2✔
145
                }
2✔
146
                Err(err) => {
×
147
                    error!("Failed to decrypt `password` for bookmark {}: {}", key, err);
×
148
                }
×
149
            }
150
        }
151
        // Decrypt AWS-S3 params
152
        if let Some(s3) = entry.s3.as_mut() {
5✔
153
            // Access key
154
            if let Some(access_key) = s3.access_key.as_mut() {
2✔
155
                match self.decrypt_str(access_key.as_str()) {
1✔
156
                    Ok(plain) => {
1✔
157
                        *access_key = plain;
1✔
158
                    }
1✔
159
                    Err(err) => {
×
160
                        error!(
×
161
                            "Failed to decrypt `access_key` for bookmark {}: {}",
162
                            key, err
163
                        );
164
                    }
×
165
                }
166
            }
167
            // Secret access key
168
            if let Some(secret_access_key) = s3.secret_access_key.as_mut() {
2✔
169
                match self.decrypt_str(secret_access_key.as_str()) {
1✔
170
                    Ok(plain) => {
1✔
171
                        *secret_access_key = plain;
1✔
172
                    }
1✔
173
                    Err(err) => {
×
174
                        error!(
×
175
                            "Failed to decrypt `secret_access_key` for bookmark {}: {}",
176
                            key, err
177
                        );
178
                    }
×
179
                }
180
            }
181
        }
182
        // Then convert into
183
        Some(FileTransferParams::from(entry))
5✔
184
    }
7✔
185

186
    /// Add a new recent to bookmarks
187
    pub fn add_bookmark<S: AsRef<str>>(
8✔
188
        &mut self,
189
        name: S,
190
        params: FileTransferParams,
191
        save_password: bool,
192
    ) {
193
        let name: String = name.as_ref().to_string();
8✔
194
        if name.is_empty() {
8✔
195
            error!("Fatal error; bookmark name is empty");
2✔
196
            panic!("Bookmark name can't be empty");
2✔
197
        }
198
        // Make bookmark
199
        info!("Added bookmark {}", name);
6✔
200
        let mut host: Bookmark = self.make_bookmark(params);
6✔
201
        // If not save_password, set secrets to `None`
202
        if !save_password {
6✔
203
            host.password = None;
2✔
204
            if let Some(s3) = host.s3.as_mut() {
2✔
205
                s3.access_key = None;
1✔
206
                s3.secret_access_key = None;
1✔
207
            }
208
        }
209
        self.hosts.bookmarks.insert(name, host);
6✔
210
    }
6✔
211

212
    /// Delete entry from bookmarks
213
    pub fn del_bookmark(&mut self, name: &str) {
1✔
214
        let _ = self.hosts.bookmarks.remove(name);
1✔
215
        info!("Removed bookmark {}", name);
1✔
216
    }
1✔
217
    /// Iterate over recents keys
218
    pub fn iter_recents(&self) -> impl Iterator<Item = &String> + '_ {
8✔
219
        Box::new(self.hosts.recents.keys())
8✔
220
    }
8✔
221

222
    /// Get recent associated to key
223
    pub fn get_recent(&self, key: &str) -> Option<FileTransferParams> {
3✔
224
        // NOTE: password is not decrypted; recents will never have password
225
        info!("Getting bookmark {}", key);
3✔
226
        let entry: Bookmark = self.hosts.recents.get(key).cloned()?;
3✔
227
        Some(FileTransferParams::from(entry))
3✔
228
    }
3✔
229

230
    /// Add a new recent to bookmarks
231
    pub fn add_recent(&mut self, params: FileTransferParams) {
8✔
232
        // Make bookmark
233
        let mut host: Bookmark = self.make_bookmark(params);
8✔
234
        // Null password for recents
235
        host.password = None;
8✔
236
        if let Some(s3) = host.s3.as_mut() {
8✔
237
            s3.access_key = None;
1✔
238
            s3.secret_access_key = None;
1✔
239
        }
240
        // Check if duplicated
241
        for (key, value) in &self.hosts.recents {
11✔
242
            if *value == host {
4✔
243
                debug!("Discarding recent since duplicated ({})", key);
1✔
244
                // Don't save duplicates
245
                return;
246
            }
247
        }
248
        // If hosts size is bigger than self.recents_size; pop last
249
        if self.hosts.recents.len() >= self.recents_size {
7✔
250
            // Get keys
251
            let mut keys: Vec<String> = Vec::with_capacity(self.hosts.recents.len());
1✔
252
            for key in self.hosts.recents.keys() {
3✔
253
                keys.push(key.clone());
2✔
254
            }
255
            // Sort keys; NOTE: most recent is the last element
256
            keys.sort();
1✔
257
            // Delete keys starting from the last one
258
            for key in keys.iter() {
2✔
259
                let _ = self.hosts.recents.remove(key);
1✔
260
                debug!("Removed recent bookmark {}", key);
1✔
261
                // If length is < self.recents_size; break
262
                if self.hosts.recents.len() < self.recents_size {
1✔
263
                    break;
264
                }
265
            }
266
        }
1✔
267
        let name: String = fmt_time(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
7✔
268
        info!("Saved recent host {}", name);
7✔
269
        self.hosts.recents.insert(name, host);
7✔
270
    }
8✔
271

272
    /// Delete entry from recents
273
    pub fn del_recent(&mut self, name: &str) {
1✔
274
        let _ = self.hosts.recents.remove(name);
1✔
275
        info!("Removed recent host {}", name);
1✔
276
    }
1✔
277

278
    /// Write bookmarks to file
279
    pub fn write_bookmarks(&self) -> Result<(), SerializerError> {
18✔
280
        // Open file
281
        debug!("Writing bookmarks");
18✔
282
        match OpenOptions::new()
36✔
283
            .create(true)
284
            .write(true)
285
            .truncate(true)
286
            .open(self.bookmarks_file.as_path())
18✔
287
        {
288
            Ok(writer) => serialize(&self.hosts, Box::new(writer)),
18✔
289
            Err(err) => {
×
290
                error!("Failed to write bookmarks: {}", err);
×
291
                Err(SerializerError::new_ex(
×
292
                    SerializerErrorKind::Io,
×
293
                    err.to_string(),
×
294
                ))
295
            }
×
296
        }
297
    }
18✔
298

299
    /// Read bookmarks from file
300
    fn read_bookmarks(&mut self) -> Result<(), SerializerError> {
1✔
301
        // Open bookmarks file for read
302
        debug!("Reading bookmarks");
1✔
303
        match OpenOptions::new()
2✔
304
            .read(true)
305
            .open(self.bookmarks_file.as_path())
1✔
306
        {
307
            Ok(reader) => {
1✔
308
                // Deserialize
309
                match deserialize(Box::new(reader)) {
1✔
310
                    Ok(hosts) => {
1✔
311
                        self.hosts = hosts;
1✔
312
                        Ok(())
1✔
313
                    }
1✔
314
                    Err(err) => Err(err),
×
315
                }
316
            }
317
            Err(err) => {
×
318
                error!("Failed to read bookmarks: {}", err);
×
319
                Err(SerializerError::new_ex(
×
320
                    SerializerErrorKind::Io,
×
321
                    err.to_string(),
×
322
                ))
323
            }
×
324
        }
325
    }
1✔
326

327
    /// Generate a new AES key
328
    fn generate_key() -> String {
15✔
329
        // Generate 256 bytes (2048 bits) key
330
        random_alphanumeric_with_len(256)
15✔
331
    }
15✔
332

333
    /// Make bookmark from credentials
334
    fn make_bookmark(&self, params: FileTransferParams) -> Bookmark {
14✔
335
        let mut bookmark: Bookmark = Bookmark::from(params);
14✔
336
        // Encrypt password
337
        if let Some(pwd) = bookmark.password {
14✔
338
            bookmark.password = Some(self.encrypt_str(pwd.as_str()));
11✔
339
        }
11✔
340
        // Encrypt aws s3 params
341
        if let Some(s3) = bookmark.s3.as_mut() {
14✔
342
            if let Some(access_key) = s3.access_key.as_mut() {
3✔
343
                *access_key = self.encrypt_str(access_key.as_str());
3✔
344
            }
345
            if let Some(secret_access_key) = s3.secret_access_key.as_mut() {
3✔
346
                *secret_access_key = self.encrypt_str(secret_access_key.as_str());
3✔
347
            }
348
        }
349
        bookmark
350
    }
14✔
351

352
    /// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64
353
    fn encrypt_str(&self, txt: &str) -> String {
17✔
354
        crypto::aes128_b64_crypt(self.key.as_str(), txt)
17✔
355
    }
17✔
356

357
    /// Decrypt provided string using AES-128
358
    fn decrypt_str(&self, secret: &str) -> Result<String, SerializerError> {
6✔
359
        match crypto::aes128_b64_decrypt(self.key.as_str(), secret) {
6✔
360
            Ok(txt) => Ok(txt),
5✔
361
            Err(err) => Err(SerializerError::new_ex(
2✔
362
                SerializerErrorKind::Syntax,
1✔
363
                err.to_string(),
1✔
364
            )),
1✔
365
        }
366
    }
6✔
367
}
368

369
#[cfg(test)]
370
#[cfg(not(target_os = "macos"))] // CI/CD blocks
371
mod tests {
372

373
    use super::*;
374
    use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams};
375
    use crate::filetransfer::{FileTransferProtocol, ProtocolParams};
376

377
    use pretty_assertions::assert_eq;
378
    use std::thread::sleep;
379
    use std::time::Duration;
380
    use tempfile::TempDir;
381

382
    #[test]
383

384
    fn test_system_bookmarks_new() {
2✔
385
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
386
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
387
        // Initialize a new bookmarks client
388
        let client: BookmarksClient =
389
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
390
        // Verify client
391
        assert_eq!(client.hosts.bookmarks.len(), 0);
1✔
392
        assert_eq!(client.hosts.recents.len(), 0);
1✔
393
        assert_eq!(client.key.len(), 256);
1✔
394
        assert_eq!(client.bookmarks_file, cfg_path);
1✔
395
        assert_eq!(client.recents_size, 16);
1✔
396
    }
2✔
397

398
    #[test]
399
    #[cfg(any(
400
        target_os = "linux",
401
        target_os = "freebsd",
402
        target_os = "netbsd",
403
        target_os = "openbsd"
404
    ))]
405
    fn test_system_bookmarks_new_err() {
2✔
406
        assert!(BookmarksClient::new(
1✔
407
            Path::new("/tmp/oifoif/omar"),
1✔
408
            Path::new("/tmp/efnnu/omar"),
1✔
409
            16
410
        )
411
        .is_err());
412

413
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
414
        let (cfg_path, _): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
415
        assert!(
1✔
416
            BookmarksClient::new(cfg_path.as_path(), Path::new("/tmp/efnnu/omar"), 16).is_err()
1✔
417
        );
418
    }
2✔
419

420
    #[test]
421

422
    fn test_system_bookmarks_new_from_existing() {
2✔
423
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
424
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
425
        // Initialize a new bookmarks client
426
        let mut client: BookmarksClient =
427
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
428
        // Add some bookmarks
429
        client.add_bookmark(
1✔
430
            "raspberry",
431
            make_generic_ftparams(
1✔
432
                FileTransferProtocol::Sftp,
1✔
433
                "192.168.1.31",
434
                22,
435
                "pi",
436
                Some("mypassword"),
1✔
437
            ),
438
            true,
439
        );
440
        client.add_recent(make_generic_ftparams(
1✔
441
            FileTransferProtocol::Sftp,
1✔
442
            "192.168.1.31",
443
            22,
444
            "pi",
445
            Some("mypassword"),
1✔
446
        ));
447
        let recent_key: String = String::from(client.iter_recents().next().unwrap());
1✔
448
        assert!(client.write_bookmarks().is_ok());
1✔
449
        let key: String = client.key.clone();
1✔
450
        // Re-initialize a client
451
        let client: BookmarksClient =
452
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
453
        // Verify it loaded parameters correctly
454
        assert_eq!(client.key, key);
1✔
455
        let bookmark = ftparams_to_tup(client.get_bookmark("raspberry").unwrap());
1✔
456
        assert_eq!(bookmark.0, String::from("192.168.1.31"));
1✔
457
        assert_eq!(bookmark.1, 22);
1✔
458
        assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
1✔
459
        assert_eq!(bookmark.3, String::from("pi"));
1✔
460
        assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword"));
1✔
461
        let bookmark = ftparams_to_tup(client.get_recent(&recent_key).unwrap());
1✔
462
        assert_eq!(bookmark.0, String::from("192.168.1.31"));
1✔
463
        assert_eq!(bookmark.1, 22);
1✔
464
        assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
1✔
465
        assert_eq!(bookmark.3, String::from("pi"));
1✔
466
        assert_eq!(bookmark.4, None);
1✔
467
    }
2✔
468

469
    #[test]
470
    fn should_make_s3_bookmark_with_secrets() {
2✔
471
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
472
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
473
        // Initialize a new bookmarks client
474
        let mut client: BookmarksClient =
475
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
476
        // Add s3 bookmark
477
        client.add_bookmark("my-bucket", make_s3_ftparams(), true);
1✔
478
        // Verify bookmark
479
        let bookmark = client.get_bookmark("my-bucket").unwrap();
1✔
480
        assert_eq!(bookmark.protocol, FileTransferProtocol::AwsS3);
1✔
481
        let params = bookmark.params.s3_params().unwrap();
1✔
482
        assert_eq!(params.access_key.as_deref().unwrap(), "pippo");
1✔
483
        assert_eq!(params.profile.as_deref().unwrap(), "test");
1✔
484
        assert_eq!(params.secret_access_key.as_deref().unwrap(), "pluto");
1✔
485
        assert_eq!(params.bucket_name.as_str(), "omar");
1✔
486
        assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
1✔
487
    }
2✔
488

489
    #[test]
490
    fn should_make_s3_bookmark_without_secrets() {
2✔
491
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
492
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
493
        // Initialize a new bookmarks client
494
        let mut client: BookmarksClient =
495
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
496
        // Add s3 bookmark
497
        client.add_bookmark("my-bucket", make_s3_ftparams(), false);
1✔
498
        // Verify bookmark
499
        let bookmark = client.get_bookmark("my-bucket").unwrap();
1✔
500
        assert_eq!(bookmark.protocol, FileTransferProtocol::AwsS3);
1✔
501
        let params = bookmark.params.s3_params().unwrap();
1✔
502
        assert_eq!(params.profile.as_deref().unwrap(), "test");
1✔
503
        assert_eq!(params.bucket_name.as_str(), "omar");
1✔
504
        assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
1✔
505
        // secrets
506
        assert_eq!(params.access_key, None);
1✔
507
        assert_eq!(params.secret_access_key, None);
1✔
508
    }
2✔
509

510
    #[test]
511
    fn should_make_s3_recent() {
2✔
512
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
513
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
514
        // Initialize a new bookmarks client
515
        let mut client: BookmarksClient =
516
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
517
        // Add s3 bookmark
518
        client.add_recent(make_s3_ftparams());
1✔
519
        // Verify bookmark
520
        let bookmark = client.iter_recents().next().unwrap();
1✔
521
        let bookmark = client.get_recent(bookmark).unwrap();
1✔
522
        assert_eq!(bookmark.protocol, FileTransferProtocol::AwsS3);
1✔
523
        let params = bookmark.params.s3_params().unwrap();
1✔
524
        assert_eq!(params.profile.as_deref().unwrap(), "test");
1✔
525
        assert_eq!(params.bucket_name.as_str(), "omar");
1✔
526
        assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
1✔
527
        // secrets
528
        assert_eq!(params.access_key, None);
1✔
529
        assert_eq!(params.secret_access_key, None);
1✔
530
    }
2✔
531

532
    #[test]
533

534
    fn test_system_bookmarks_manipulate_bookmarks() {
2✔
535
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
536
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
537
        // Initialize a new bookmarks client
538
        let mut client: BookmarksClient =
539
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
540
        // Add bookmark
541
        client.add_bookmark(
1✔
542
            "raspberry",
543
            make_generic_ftparams(
1✔
544
                FileTransferProtocol::Sftp,
1✔
545
                "192.168.1.31",
546
                22,
547
                "pi",
548
                Some("mypassword"),
1✔
549
            ),
550
            true,
551
        );
552
        client.add_bookmark(
1✔
553
            "raspberry2",
554
            make_generic_ftparams(
1✔
555
                FileTransferProtocol::Sftp,
1✔
556
                "192.168.1.31",
557
                22,
558
                "pi",
559
                Some("mypassword2"),
1✔
560
            ),
561
            true,
562
        );
563
        // Iter
564
        assert_eq!(client.iter_bookmarks().count(), 2);
1✔
565
        // Get bookmark
566
        let bookmark = ftparams_to_tup(client.get_bookmark(&String::from("raspberry")).unwrap());
1✔
567
        assert_eq!(bookmark.0, String::from("192.168.1.31"));
1✔
568
        assert_eq!(bookmark.1, 22);
1✔
569
        assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
1✔
570
        assert_eq!(bookmark.3, String::from("pi"));
1✔
571
        assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword"));
1✔
572
        // Write bookmarks
573
        assert!(client.write_bookmarks().is_ok());
1✔
574
        // Delete bookmark
575
        client.del_bookmark(&String::from("raspberry"));
1✔
576
        // Get unexisting bookmark
577
        assert!(client.get_bookmark(&String::from("raspberry")).is_none());
1✔
578
        // Write bookmarks
579
        assert!(client.write_bookmarks().is_ok());
1✔
580
    }
2✔
581

582
    #[test]
583
    #[should_panic]
584

585
    fn test_system_bookmarks_bad_bookmark_name() {
2✔
586
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
587
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
588
        // Initialize a new bookmarks client
589
        let mut client: BookmarksClient =
590
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
591
        // Add bookmark
592
        client.add_bookmark(
1✔
593
            "",
594
            make_generic_ftparams(
1✔
595
                FileTransferProtocol::Sftp,
1✔
596
                "192.168.1.31",
597
                22,
598
                "pi",
599
                Some("mypassword"),
1✔
600
            ),
601
            true,
602
        );
603
    }
2✔
604

605
    #[test]
606
    fn save_bookmark_wno_password() {
2✔
607
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
608
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
609
        // Initialize a new bookmarks client
610
        let mut client: BookmarksClient =
611
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
612
        // Add bookmark
613
        client.add_bookmark(
1✔
614
            "raspberry",
615
            make_generic_ftparams(
1✔
616
                FileTransferProtocol::Sftp,
1✔
617
                "192.168.1.31",
618
                22,
619
                "pi",
620
                Some("mypassword"),
1✔
621
            ),
622
            false,
623
        );
624
        let bookmark = ftparams_to_tup(client.get_bookmark(&String::from("raspberry")).unwrap());
1✔
625
        assert_eq!(bookmark.0, String::from("192.168.1.31"));
1✔
626
        assert_eq!(bookmark.1, 22);
1✔
627
        assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
1✔
628
        assert_eq!(bookmark.3, String::from("pi"));
1✔
629
        assert_eq!(bookmark.4, None);
1✔
630
    }
2✔
631

632
    #[test]
633

634
    fn test_system_bookmarks_manipulate_recents() {
2✔
635
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
636
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
637
        // Initialize a new bookmarks client
638
        let mut client: BookmarksClient =
639
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
640
        // Add bookmark
641
        client.add_recent(make_generic_ftparams(
1✔
642
            FileTransferProtocol::Sftp,
1✔
643
            "192.168.1.31",
644
            22,
645
            "pi",
646
            Some("mypassword"),
1✔
647
        ));
648
        // Iter
649
        assert_eq!(client.iter_recents().count(), 1);
1✔
650
        let key: String = String::from(client.iter_recents().next().unwrap());
1✔
651
        // Get bookmark
652
        let bookmark = ftparams_to_tup(client.get_recent(&key).unwrap());
1✔
653
        assert_eq!(bookmark.0, String::from("192.168.1.31"));
1✔
654
        assert_eq!(bookmark.1, 22);
1✔
655
        assert_eq!(bookmark.2, FileTransferProtocol::Sftp);
1✔
656
        assert_eq!(bookmark.3, String::from("pi"));
1✔
657
        assert_eq!(bookmark.4, None);
1✔
658
        // Write bookmarks
659
        assert!(client.write_bookmarks().is_ok());
1✔
660
        // Delete bookmark
661
        client.del_recent(&key);
1✔
662
        // Get unexisting bookmark
663
        assert!(client.get_bookmark(&key).is_none());
1✔
664
        // Write bookmarks
665
        assert!(client.write_bookmarks().is_ok());
1✔
666
    }
2✔
667

668
    #[test]
669

670
    fn test_system_bookmarks_dup_recent() {
2✔
671
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
672
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
673
        // Initialize a new bookmarks client
674
        let mut client: BookmarksClient =
675
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
676
        // Add bookmark
677
        client.add_recent(make_generic_ftparams(
1✔
678
            FileTransferProtocol::Sftp,
1✔
679
            "192.168.1.31",
680
            22,
681
            "pi",
682
            Some("mypassword"),
1✔
683
        ));
684
        client.add_recent(make_generic_ftparams(
1✔
685
            FileTransferProtocol::Sftp,
1✔
686
            "192.168.1.31",
687
            22,
688
            "pi",
689
            Some("mypassword"),
1✔
690
        ));
691
        // There should be only one recent
692
        assert_eq!(client.iter_recents().count(), 1);
1✔
693
    }
2✔
694

695
    #[test]
696

697
    fn test_system_bookmarks_recents_more_than_limit() {
2✔
698
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
699
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
700
        // Initialize a new bookmarks client
701
        let mut client: BookmarksClient =
702
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 2).unwrap();
1✔
703
        // Add recent, wait 1 second for each one (cause the name depends on time)
704
        // 1
705
        client.add_recent(make_generic_ftparams(
1✔
706
            FileTransferProtocol::Sftp,
1✔
707
            "192.168.1.1",
708
            22,
709
            "pi",
710
            Some("mypassword"),
1✔
711
        ));
712
        sleep(Duration::from_secs(1));
1✔
713
        // 2
714
        client.add_recent(make_generic_ftparams(
1✔
715
            FileTransferProtocol::Sftp,
1✔
716
            "192.168.1.2",
717
            22,
718
            "pi",
719
            Some("mypassword"),
1✔
720
        ));
721
        sleep(Duration::from_secs(1));
1✔
722
        // 3
723
        client.add_recent(make_generic_ftparams(
1✔
724
            FileTransferProtocol::Sftp,
1✔
725
            "192.168.1.3",
726
            22,
727
            "pi",
728
            Some("mypassword"),
1✔
729
        ));
730
        // Limit is 2
731
        assert_eq!(client.iter_recents().count(), 2);
1✔
732
        // Check that 192.168.1.1 has been removed
733
        let key: String = client.iter_recents().nth(0).unwrap().to_string();
1✔
734
        assert!(matches!(
1✔
735
            client
1✔
736
                .hosts
737
                .recents
738
                .get(&key)
739
                .unwrap()
740
                .address
741
                .as_ref()
742
                .cloned()
743
                .unwrap_or_default()
744
                .as_str(),
745
            "192.168.1.2" | "192.168.1.3"
1✔
746
        ));
747
        let key: String = client.iter_recents().nth(1).unwrap().to_string();
1✔
748
        assert!(matches!(
1✔
749
            client
1✔
750
                .hosts
751
                .recents
752
                .get(&key)
753
                .unwrap()
754
                .address
755
                .as_ref()
756
                .cloned()
757
                .unwrap_or_default()
758
                .as_str(),
759
            "192.168.1.2" | "192.168.1.3"
1✔
760
        ));
761
    }
2✔
762

763
    #[test]
764
    #[should_panic]
765
    fn test_system_bookmarks_add_bookmark_empty() {
2✔
766
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
767
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
768
        // Initialize a new bookmarks client
769
        let mut client: BookmarksClient =
770
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
771
        // Add bookmark
772
        client.add_bookmark(
1✔
773
            "",
774
            make_generic_ftparams(
1✔
775
                FileTransferProtocol::Sftp,
1✔
776
                "192.168.1.31",
777
                22,
778
                "pi",
779
                Some("mypassword"),
1✔
780
            ),
781
            true,
782
        );
783
    }
2✔
784

785
    #[test]
786
    fn test_system_bookmarks_decrypt_str() {
2✔
787
        let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap();
1✔
788
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
789
        // Initialize a new bookmarks client
790
        let mut client: BookmarksClient =
791
            BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap();
1✔
792
        client.key = "MYSUPERSECRETKEY".to_string();
1✔
793
        assert_eq!(
1✔
794
            client.decrypt_str("z4Z6LpcpYqBW4+bkIok+5A==").ok().unwrap(),
1✔
795
            "Hello world!"
796
        );
797
        assert!(client.decrypt_str("bidoof").is_err());
1✔
798
    }
2✔
799

800
    /// Get paths for configuration and key for bookmarks
801
    fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
14✔
802
        let k: PathBuf = PathBuf::from(dir);
14✔
803
        let mut c: PathBuf = k.clone();
14✔
804
        c.push("bookmarks.toml");
14✔
805
        (c, k)
14✔
806
    }
14✔
807

808
    fn make_generic_ftparams(
13✔
809
        protocol: FileTransferProtocol,
810
        address: &str,
811
        port: u16,
812
        username: &str,
813
        password: Option<&str>,
814
    ) -> FileTransferParams {
815
        let params = ProtocolParams::Generic(
13✔
816
            GenericProtocolParams::default()
26✔
817
                .address(address)
818
                .port(port)
819
                .username(Some(username))
13✔
820
                .password(password),
821
        );
822
        FileTransferParams::new(protocol, params)
13✔
823
    }
13✔
824

825
    fn make_s3_ftparams() -> FileTransferParams {
3✔
826
        FileTransferParams::new(
3✔
827
            FileTransferProtocol::AwsS3,
3✔
828
            ProtocolParams::AwsS3(
3✔
829
                AwsS3Params::new("omar", Some("eu-west-1"), Some("test"))
18✔
830
                    .endpoint(Some("http://localhost:9000"))
3✔
831
                    .new_path_style(false)
832
                    .access_key(Some("pippo"))
3✔
833
                    .secret_access_key(Some("pluto"))
3✔
834
                    .security_token(Some("omar"))
3✔
835
                    .session_token(Some("gerry-scotti")),
3✔
836
            ),
837
        )
838
    }
3✔
839

840
    fn ftparams_to_tup(
5✔
841
        params: FileTransferParams,
842
    ) -> (String, u16, FileTransferProtocol, String, Option<String>) {
843
        let protocol = params.protocol;
5✔
844
        let p = params.params.generic_params().unwrap();
5✔
845
        (
5✔
846
            p.address.to_string(),
5✔
847
            p.port,
5✔
848
            protocol,
849
            p.username.as_ref().cloned().unwrap_or_default(),
5✔
850
            p.password.as_ref().cloned(),
5✔
851
        )
852
    }
5✔
853
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc