aboutsummaryrefslogtreecommitdiff
path: root/ext/otp-cache/src/ext.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/otp-cache/src/ext.rs')
-rw-r--r--ext/otp-cache/src/ext.rs145
1 files changed, 145 insertions, 0 deletions
diff --git a/ext/otp-cache/src/ext.rs b/ext/otp-cache/src/ext.rs
new file mode 100644
index 0000000..9ed965b
--- /dev/null
+++ b/ext/otp-cache/src/ext.rs
@@ -0,0 +1,145 @@
+// ext.rs
+
+// Copyright (C) 2020 The Nitrocli Developers
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+use std::env;
+use std::ffi;
+use std::fmt;
+use std::process;
+use std::str;
+
+use anyhow::Context as _;
+
+pub struct Context {
+ /// The path to the nitrocli binary.
+ pub nitrocli: ffi::OsString,
+ /// The nitrokey model to use.
+ pub model: nitrokey::Model,
+ /// The verbosity level to use.
+ pub verbosity: u8,
+}
+
+impl Context {
+ pub fn from_env() -> anyhow::Result<Self> {
+ let nitrocli = env::var_os("NITROCLI_BINARY")
+ .context("NITROCLI_BINARY environment variable not present")
+ .context("Failed to retrieve nitrocli path")?;
+
+ let model = env::var_os("NITROCLI_MODEL")
+ .context("NITROCLI_MODEL environment variable not present")
+ .context("Failed to retrieve nitrocli model")?;
+ let model = model
+ .to_str()
+ .context("Provided model string is not valid UTF-8")?;
+ let model = match model {
+ "pro" => nitrokey::Model::Pro,
+ "storage" => nitrokey::Model::Storage,
+ _ => anyhow::bail!("Provided model is not valid: '{}'", model),
+ };
+
+ let verbosity = env::var_os("NITROCLI_VERBOSITY")
+ .context("NITROCLI_VERBOSITY environment variable not present")
+ .context("Failed to retrieve nitrocli verbosity")?;
+ let verbosity = verbosity
+ .to_str()
+ .context("Provided verbosity string is not valid UTF-8")?;
+ let verbosity = u8::from_str_radix(verbosity, 10).context("Failed to parse verbosity")?;
+
+ Ok(Self {
+ nitrocli,
+ model,
+ verbosity,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub struct Nitrocli {
+ cmd: process::Command,
+}
+
+impl Nitrocli {
+ pub fn from_context(ctx: &Context) -> Nitrocli {
+ Self {
+ cmd: process::Command::new(&ctx.nitrocli),
+ }
+ }
+
+ pub fn arg(&mut self, arg: impl AsRef<ffi::OsStr>) -> &mut Nitrocli {
+ self.cmd.arg(arg);
+ self
+ }
+
+ pub fn args<I, S>(&mut self, args: I) -> &mut Nitrocli
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<ffi::OsStr>,
+ {
+ self.cmd.args(args);
+ self
+ }
+
+ pub fn text(&mut self) -> anyhow::Result<String> {
+ let output = self.cmd.output().context("Failed to invoke nitrocli")?;
+ if output.status.success() {
+ String::from_utf8(output.stdout).map_err(From::from)
+ } else {
+ Err(anyhow::anyhow!(
+ "nitrocli call failed: {}",
+ String::from_utf8_lossy(&output.stderr)
+ ))
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum OtpAlgorithm {
+ Hotp,
+ Totp,
+}
+
+impl fmt::Display for OtpAlgorithm {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ OtpAlgorithm::Hotp => "hotp",
+ OtpAlgorithm::Totp => "totp",
+ }
+ )
+ }
+}
+
+impl str::FromStr for OtpAlgorithm {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<OtpAlgorithm, Self::Err> {
+ match s {
+ "hotp" => Ok(OtpAlgorithm::Hotp),
+ "totp" => Ok(OtpAlgorithm::Totp),
+ _ => Err(anyhow::anyhow!("Unexpected OTP algorithm: {}", s)),
+ }
+ }
+}
+
+impl<'de> serde::Deserialize<'de> for OtpAlgorithm {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ use serde::de::Error as _;
+
+ str::FromStr::from_str(&String::deserialize(deserializer)?).map_err(D::Error::custom)
+ }
+}
+
+impl serde::Serialize for OtpAlgorithm {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ self.to_string().serialize(serializer)
+ }
+}