use std::{ io::{self, Cursor}, path::{Path, PathBuf}, }; use regex::Regex; use serde::{Deserialize, Serialize}; use tokio::{fs::File, io::AsyncRead}; use validator::{Validate, ValidationError}; #[derive(Debug, Clone, Serialize, Deserialize)] enum KeySource { #[serde(rename = "text")] Text(String), #[serde(rename = "keyCommand")] Command(Vec), #[serde(rename = "keyFile")] File(PathBuf), } #[derive(Debug, Clone, Validate, Serialize, Deserialize)] pub struct Key { #[serde(flatten)] source: KeySource, #[validate(custom = "validate_dest_dir")] #[serde(rename = "destDir")] dest_dir: PathBuf, #[validate(custom = "validate_unix_name")] user: String, #[validate(custom = "validate_unix_name")] group: String, permissions: String, } impl Key { pub async fn reader(&'_ self,) -> Result, io::Error> { match &self.source { KeySource::Text(content) => { Ok(Box::new(Cursor::new(content))) } KeySource::Command(_command) => { todo!("Implement keyCommand support") } KeySource::File(path) => { Ok(Box::new(File::open(path).await?)) } } } pub fn dest_dir(&self) -> &Path { &self.dest_dir } pub fn user(&self) -> &str { &self.user } pub fn group(&self) -> &str { &self.user } pub fn permissions(&self) -> &str { &self.permissions } } fn validate_unix_name(name: &str) -> Result<(), ValidationError> { let re = Regex::new(r"^[a-z][-a-z0-9]*$").unwrap(); if re.is_match(name) { Ok(()) } else { Err(ValidationError::new("Invalid user/group name")) } } fn validate_dest_dir(dir: &PathBuf) -> Result<(), ValidationError> { if dir.has_root() { Ok(()) } else { Err(ValidationError::new("Secret key destination directory must be absolute")) } }