diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/args.rs | 10 | ||||
-rw-r--r-- | src/commands.rs | 25 | ||||
-rw-r--r-- | src/config.rs | 25 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/tests/status.rs | 10 |
5 files changed, 66 insertions, 5 deletions
diff --git a/src/args.rs b/src/args.rs index 3052afa..0d77806 100644 --- a/src/args.rs +++ b/src/args.rs @@ -13,6 +13,16 @@ pub struct Args { /// Selects the device model to connect to #[structopt(short, long, global = true, possible_values = &DeviceModel::all_str())] pub model: Option<DeviceModel>, + /// Sets the serial number of the device to connect to. Can be set + /// multiple times to allow multiple serial numbers + // TODO: Add short options (avoid collisions). + #[structopt( + long = "serial-number", + global = true, + multiple = true, + number_of_values = 1 + )] + pub serial_numbers: Vec<nitrokey::SerialNumber>, /// Disables the cache for all secrets. #[structopt(long, global = true)] pub no_cache: bool, diff --git a/src/commands.rs b/src/commands.rs index 05038e0..f718571 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -43,10 +43,22 @@ fn set_log_level(ctx: &mut Context<'_>) { /// Create a filter string from the program configuration. fn format_filter(config: &config::Config) -> String { + let mut filters = Vec::new(); if let Some(model) = config.model { - format!(" (filter: model={})", model.as_ref()) - } else { + filters.push(format!("model={}", model.as_ref())); + } + if !config.serial_numbers.is_empty() { + let serial_numbers = config + .serial_numbers + .iter() + .map(ToString::to_string) + .collect::<Vec<_>>(); + filters.push(format!("serial number in [{}]", serial_numbers.join(", "))); + } + if filters.is_empty() { String::new() + } else { + format!(" (filter: {})", filters.join(", ")) } } @@ -56,7 +68,14 @@ fn find_device(config: &config::Config) -> anyhow::Result<nitrokey::DeviceInfo> let nkmodel = config.model.map(nitrokey::Model::from); let mut iter = devices .into_iter() - .filter(|device| nkmodel.is_none() || device.model == nkmodel); + .filter(|device| nkmodel.is_none() || device.model == nkmodel) + .filter(|device| { + config.serial_numbers.is_empty() + || device + .serial_number + .map(|sn| config.serial_numbers.contains(&sn)) + .unwrap_or_default() + }); let device = iter .next() diff --git a/src/config.rs b/src/config.rs index aceda38..6f0cd17 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,9 @@ use std::fs; use std::path; +use std::str::FromStr as _; + +use serde::de::Error as _; use crate::args; @@ -20,10 +23,14 @@ const CONFIG_FILE: &str = "config.toml"; /// The configuration for nitrocli, usually read from configuration /// files and environment variables. -#[derive(Clone, Copy, Debug, Default, PartialEq, merge::Merge, serde::Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, merge::Merge, serde::Deserialize)] pub struct Config { /// The model to connect to. pub model: Option<args::DeviceModel>, + /// The serial numbers of the device to connect to. + #[merge(strategy = merge::vec::overwrite_empty)] + #[serde(default, deserialize_with = "deserialize_serial_number_vec")] + pub serial_numbers: Vec<nitrokey::SerialNumber>, /// Whether to bypass the cache for all secrets or not. #[merge(strategy = merge::bool::overwrite_false)] #[serde(default)] @@ -34,6 +41,18 @@ pub struct Config { pub verbosity: u8, } +fn deserialize_serial_number_vec<'de, D>(d: D) -> Result<Vec<nitrokey::SerialNumber>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let strings: Vec<String> = serde::Deserialize::deserialize(d).map_err(D::Error::custom)?; + let result: Result<Vec<_>, _> = strings + .iter() + .map(|s| nitrokey::SerialNumber::from_str(s)) + .collect(); + result.map_err(D::Error::custom) +} + impl Config { pub fn load() -> anyhow::Result<Self> { use merge::Merge as _; @@ -51,6 +70,10 @@ impl Config { if args.model.is_some() { self.model = args.model; } + if !args.serial_numbers.is_empty() { + // TODO: Don't clone. + self.serial_numbers = args.serial_numbers.clone(); + } if args.no_cache { self.no_cache = true; } diff --git a/src/main.rs b/src/main.rs index 16715f9..baad15c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ // Copyright (C) 2017-2020 The Nitrocli Developers // SPDX-License-Identifier: GPL-3.0-or-later -#![allow(clippy::trivially_copy_pass_by_ref)] #![warn( bad_style, dead_code, diff --git a/src/tests/status.rs b/src/tests/status.rs index 7946929..268e36f 100644 --- a/src/tests/status.rs +++ b/src/tests/status.rs @@ -28,6 +28,16 @@ fn not_found_pro() { assert_eq!(err, "Nitrokey device not found (filter: model=pro)"); } +#[test_device] +fn not_found_by_serial_number() { + let res = Nitrocli::new().handle(&["status", "--model=storage", "--serial-number=deadbeef"]); + let err = res.unwrap_err().to_string(); + assert_eq!( + err, + "Nitrokey device not found (filter: model=storage, serial number in [0xdeadbeef])" + ); +} + #[test_device(pro)] fn output_pro(model: nitrokey::Model) -> anyhow::Result<()> { let re = regex::Regex::new( |