1
0
Fork 0
mirror of https://codeberg.org/tyy/aspm synced 2024-12-22 20:39:29 -07:00

Formatting, tests

This commit is contained in:
Tyler Beckman 2024-07-08 17:45:26 -04:00
parent afa8eb79b3
commit 61baa8f52f
Signed by: Ty
GPG key ID: 2813440C772555A4
10 changed files with 141 additions and 22 deletions

View file

@ -166,4 +166,10 @@ mod tests {
let result = AspeServer::new(Host::Domain(String::from("keyoxide.org"))).await; let result = AspeServer::new(Host::Domain(String::from("keyoxide.org"))).await;
assert!(result.is_ok(), "Constructing an AspeServer should succeed") assert!(result.is_ok(), "Constructing an AspeServer should succeed")
} }
#[tokio::test]
async fn building_invalid_aspe_server_fails() {
let result = AspeServer::new(Host::Domain(String::from("example.com"))).await;
assert!(result.is_err(), "Constructing an AspeServer with an invalid domain should fail")
}
} }

View file

@ -3,7 +3,8 @@ use anyhow::{anyhow, bail, Context};
use argon2::{password_hash::SaltString, Argon2, PasswordHasher as _}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher as _};
use asp::{ use asp::{
aspe::{ aspe::{
requests::{AspeRequest, AspeRequestType, AspeRequestVariant}, AspeRequestFailure, AspeServer requests::{AspeRequest, AspeRequestType, AspeRequestVariant},
AspeRequestFailure, AspeServer,
}, },
hex_color::HexColor, hex_color::HexColor,
keys::AspKey, keys::AspKey,
@ -130,14 +131,20 @@ impl AspmSubcommand for AspeCreateCommand {
.context("Unable to encode the profile as a JWT and sign it")?; .context("Unable to encode the profile as a JWT and sign it")?;
match server.post_request(encoded_request).await { match server.post_request(encoded_request).await {
Ok(_) => { Ok(_) => {
println!("Successfully uploaded profile!"); println!("Successfully uploaded profile!");
Ok(()) Ok(())
}, }
Err(AspeRequestFailure::BadRequest) => bail!("The ASPE server rejected the request due to invalid data"), Err(AspeRequestFailure::BadRequest) => {
Err(AspeRequestFailure::TooLarge) => bail!("The ASPE server rejected the request as being too large"), bail!("The ASPE server rejected the request due to invalid data")
Err(AspeRequestFailure::RateLimited) => bail!("The ASPE server rejected the request due to a ratelimit"), }
Err(AspeRequestFailure::Unknown(e)) => Err(e.into()) Err(AspeRequestFailure::TooLarge) => {
} bail!("The ASPE server rejected the request as being too large")
}
Err(AspeRequestFailure::RateLimited) => {
bail!("The ASPE server rejected the request due to a ratelimit")
}
Err(AspeRequestFailure::Unknown(e)) => Err(e.into()),
}
} }
} }

View file

@ -11,5 +11,5 @@ pub struct AspeSubcommand {
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum AspeSubcommands { pub enum AspeSubcommands {
Create(create::AspeCreateCommand), Create(create::AspeCreateCommand),
} }

View file

@ -8,10 +8,7 @@ use sea_orm::ModelTrait as _;
use std::io::Write; use std::io::Write;
use crate::{ use crate::{commands::AspmSubcommand, utils};
commands::AspmSubcommand,
utils,
};
/// Deletes a saved key, after asking for confirmation. /// Deletes a saved key, after asking for confirmation.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]

View file

@ -45,7 +45,7 @@ pub enum KeyExportFormat {
Jwk, Jwk,
} }
/// Exports a saved key, specified by its fingerprint, into multiple formats. Run this command with `--help` in order to see a list of the possible formats, and explanations for all of them. /// Exports a saved key, specified by its fingerprint, into multiple formats. Run this command with `--help` in order to see a list of the possible formats, and explanations for all of them. This command will by default interactively prompt for the key's password in order to be decrypted, unless the KEY_PASSWORD environment variable is present.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
pub struct KeysExportCommand { pub struct KeysExportCommand {
/// The format to export the key into. All of these formats can then be correctly imported back into this tool, but only some are encrypted, so keep that in mind when handling the exported keys. /// The format to export the key into. All of these formats can then be correctly imported back into this tool, but only some are encrypted, so keep that in mind when handling the exported keys.

View file

@ -1,6 +1,6 @@
pub mod aspe;
pub mod keys; pub mod keys;
pub mod profiles; pub mod profiles;
pub mod aspe;
use clap::Parser; use clap::Parser;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;

View file

@ -32,7 +32,8 @@ impl AspmSubcommand for ProfilesImportCommand {
(host.to_string(), fingerprint.to_string()) (host.to_string(), fingerprint.to_string())
}) })
}) { }) {
Some((host, fingerprint)) => match aspe::AspeServer::new(Host::parse(&host).unwrap()).await? Some((host, fingerprint)) => match aspe::AspeServer::new(Host::parse(&host).unwrap())
.await?
.fetch_profile(&fingerprint) .fetch_profile(&fingerprint)
.await .await
{ {

View file

@ -7,7 +7,9 @@ use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
use anyhow::Context; use anyhow::Context;
use app_dirs2::{AppDataType, AppInfo}; use app_dirs2::{AppDataType, AppInfo};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use commands::{aspe::AspeSubcommands, keys::KeysSubcommands, profiles::ProfilesSubcommands, AspmSubcommand}; use commands::{
aspe::AspeSubcommands, keys::KeysSubcommands, profiles::ProfilesSubcommands, AspmSubcommand,
};
use migrations::{Migrator, MigratorTrait, SchemaManager}; use migrations::{Migrator, MigratorTrait, SchemaManager};
use sea_orm::{Database, DatabaseConnection}; use sea_orm::{Database, DatabaseConnection};
use thiserror::Error; use thiserror::Error;
@ -173,7 +175,7 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
}, },
AspmSubcommands::Aspe(subcommand) => match subcommand.subcommand { AspmSubcommands::Aspe(subcommand) => match subcommand.subcommand {
AspeSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime), AspeSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime),
} },
} }
} }

View file

@ -17,9 +17,10 @@ pub mod keys {
fingerprint: S, fingerprint: S,
password: P, password: P,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let argon_salt = let argon_salt = SaltString::from_b64(
SaltString::from_b64(&BASE64_NOPAD.encode(fingerprint.as_ref().to_uppercase().as_bytes())) &BASE64_NOPAD.encode(fingerprint.as_ref().to_uppercase().as_bytes()),
.context("Unable to derive argon2 salt")?; )
.context("Unable to derive argon2 salt")?;
let argon2 = Argon2::default(); let argon2 = Argon2::default();
let hash = argon2 let hash = argon2
.hash_password(password.as_ref(), &argon_salt) .hash_password(password.as_ref(), &argon_salt)

View file

@ -4,8 +4,28 @@ use predicates::prelude::*;
use tempfile::TempDir; use tempfile::TempDir;
use std::process::Command; use std::process::Command;
static KEY_ALIAS: &str = "TESTKEY";
static KEY_PASSWORD: &str = "TESTKEYPASSWORD";
// TODO: Also test ed25519 key generation
fn assert_key_generated(datadir: &str) -> Result<(), anyhow::Error> {
Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir)
.arg("keys")
.arg("generate")
.env("KEY_PASSWORD", KEY_PASSWORD)
.arg("es256")
.arg("--key-alias")
.arg(KEY_ALIAS)
.assert()
.success()
.stdout(predicate::str::starts_with("Successfully generated a new key!"));
Ok(())
}
#[test] #[test]
fn help_prints_correctly() -> Result<(), anyhow::Error> { fn help_works() -> Result<(), anyhow::Error> {
let tempdir = TempDir::new()?; let tempdir = TempDir::new()?;
let datadir = tempdir.path().to_str().context("Tempdir path was not valid utf8")?; let datadir = tempdir.path().to_str().context("Tempdir path was not valid utf8")?;
@ -23,6 +43,13 @@ fn help_prints_correctly() -> Result<(), anyhow::Error> {
.success() .success()
.stdout(predicate::str::starts_with(env!("CARGO_PKG_DESCRIPTION"))); .stdout(predicate::str::starts_with(env!("CARGO_PKG_DESCRIPTION")));
Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir)
.arg("help")
.assert()
.success()
.stdout(predicate::str::starts_with(env!("CARGO_PKG_DESCRIPTION")));
Command::cargo_bin("aspm")? Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir) .env("ASPM_DATA_DIR", datadir)
.assert() .assert()
@ -31,3 +58,81 @@ fn help_prints_correctly() -> Result<(), anyhow::Error> {
Ok(()) Ok(())
} }
#[test]
fn keys_generate_works() -> Result<(), anyhow::Error> {
let tempdir = TempDir::new()?;
let datadir = tempdir.path().to_str().context("Tempdir path was not valid utf8")?;
assert_key_generated(datadir)
}
#[test]
fn keys_list_works() -> Result<(), anyhow::Error> {
let tempdir = TempDir::new()?;
let datadir = tempdir.path().to_str().context("Tempdir path was not valid utf8")?;
assert_key_generated(datadir)?;
Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir)
.arg("keys")
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains(KEY_ALIAS));
Ok(())
}
// This test takes a bit due to testing each export format individually, causing the password to be hashed multiple times
#[test]
fn keys_export_works() -> Result<(), anyhow::Error> {
let tempdir = TempDir::new()?;
let datadir = tempdir.path().to_str().context("Tempdir path was not valid utf8")?;
assert_key_generated(datadir)?;
for export_format in ["pkcs8", "asp-tool", "jwk"] {
Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir)
.arg("keys")
.arg("export")
.env("KEY_PASSWORD", KEY_PASSWORD)
.arg(export_format)
.arg(KEY_ALIAS)
.assert()
.success()
.stderr(predicate::str::contains("Exported key \""));
}
Ok(())
}
#[test]
fn keys_delete_works() -> Result<(), anyhow::Error> {
let tempdir = TempDir::new()?;
let datadir = tempdir.path().to_str().context("Tempdir path was not valid utf8")?;
assert_key_generated(datadir)?;
Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir)
.arg("keys")
.arg("delete")
.arg("--no-confirm")
.arg(KEY_ALIAS)
.assert()
.success()
.stdout(predicate::str::contains("Successfully deleted key with fingerprint "));
Command::cargo_bin("aspm")?
.env("ASPM_DATA_DIR", datadir)
.arg("keys")
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("Saved keys (0 total):"));
Ok(())
}