chore: add src

This commit is contained in:
Jesús Pérez Lorenzo 2021-09-01 20:27:52 +01:00
parent a7f10ba503
commit e3a23eeaf9
5 changed files with 553 additions and 0 deletions

60
src/crypt_lib.rs Normal file
View File

@ -0,0 +1,60 @@
use crate::utils::{trim_newline};
/// encrypt & decrypt content with `enc_file` using `XChaCha20Poly1305` and random nonce
///```rust
/// let texto = String::from("Esto es una prueba");
/// println!("{}",&texto);
/// let ky = String::from("an example very very secret key.");
/// let key= ky.as_str(); //Plaintext to encrypt
/// let res_enc = encrypt(&texto,key);
/// println!("encripty: {}",&res_enc);
/// let res_dec = decrypt(&res_enc,key);
/// println!("decrypt: {}",&res_dec);
///```
#[must_use]
pub fn encrypt(text: &str, key: &str) -> String {
if key.is_empty() {
return text.to_string();
}
//let txt = format!(r#"{}"#, text); //Plaintext to encrypt
let text_vec = text.as_bytes().to_vec();
//let key: &str = "an example very very secret key."; //Key will normally be chosen from keymap and provided to the encrypt_chacha() function
//let text_vec: Vec<u8> = txt_char.to_vec(); //Convert text to Vec<u8>
//Ciphertext stores the len() of encrypted content, the nonce and the actual ciphertext using bincode
match enc_file::encrypt_chacha(text_vec, key) {
//encrypt vec<u8>, returns result(Vec<u8>)
Ok(ciphertext) => base64::encode(&ciphertext),
Err(e) => {
println!("Error encrypt: {}", e);
String::from("")
}
}
}
/// decrypt content with `enc_file` using `XChaCha20Poly1305` and random nonce
#[must_use]
pub fn decrypt(text: &str, key: &str) -> String {
if key.is_empty() {
return text.to_string();
}
let mut input = text.to_owned();
trim_newline(&mut input);
match base64::decode(&input) {
Ok(inpt_text) => match enc_file::decrypt_chacha(inpt_text, key) {
Ok(res) => match String::from_utf8(res) {
Ok(result) => result,
Err(e) => {
println!("Error utf8 decode: {}", e);
String::from("")
}
},
Err(e) => {
println!("Error decrypt: {}", e);
String::from("")
}
},
Err(e) => {
println!("Error base64 decode: {}", e);
String::from("")
}
}
// println!("text: {} plain: {}",format!("{:?}", &text), format!("{:?}", plaintext)); //Check that text == plaintext
}

12
src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
/*! tkdr functions for variety of tasks */
//
// Copyright 2020, Jesús Pérez Lorenzo
/// utilities
pub mod utils;
/// Tera utilities
pub mod tera_lib;
/// Crypto & Decrypt
pub mod crypt_lib;
/// Random key
pub mod randomkey;

127
src/randomkey.rs Normal file
View File

@ -0,0 +1,127 @@
/// Random keygen form [rusty-keys](https://github.com/rideron89/rusty-keys)
///```rust
/// let random = RandomKey::default();
/// let tkn0 = random.from_template("uuid",1)[0];
/// let tkn1 = random.get_key();
/// let tkn = format!("{}\n{}\n",tkn0,&tkn1);
///```
/// `RandomKey` can be configured from Config (config.toml) with values for
/// - length: `config.auth.keys_length`,
/// - `alfanumeric_only`: `config.auth.keys_alfanumeric`,
///
#[derive(Debug, Clone)]
pub struct RandomKey {
count: u32,
length: u32,
alfanumeric_only: bool,
}
impl Default for RandomKey {
fn default() -> Self {
Self {
count: 1,
length: 16,
alfanumeric_only: false,
}
}
}
impl RandomKey {
#[allow(clippy::missing_docs_in_private_items)]
/// Configure `RandomKey`
#[must_use]
pub fn config(length: u32, alfanumeric_only: bool) -> Self {
Self {
count: 1,
length,
alfanumeric_only,
}
}
#[allow(clippy::missing_docs_in_private_items)]
/// Get a key
#[must_use]
pub fn get_key(&self) -> String {
match self
.get_list(self.length, self.count, self.alfanumeric_only)
.get(0)
{
Some(key) => key.to_owned(),
None => String::from(""),
}
}
/// Get a list of keys
#[must_use]
#[allow(
clippy::missing_docs_in_private_items,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
pub fn get_list(&self, length: u32, count: u32, allow_symbols: bool) -> Vec<String> {
let mut alphabet =
String::from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789");
if allow_symbols {
alphabet.push_str("/.;!@$");
}
let mut codes: Vec<String> = Vec::new();
for _ in 0..count {
let mut code = String::new();
for _ in 0..length {
let number = rand::random::<f32>() * (alphabet.len() as f32);
let number = number.round() as usize;
if let Some(character) = alphabet.chars().nth(number) {
code.push(character)
}
}
codes.push(code);
}
codes
}
/// Print a list of passwords for a given template.
#[must_use]
pub fn from_template(&self, template: &str, count: u32) -> Vec<String> {
let mut codes: Vec<String> = Vec::new();
for _ in 0..count {
match template {
"uuid" => codes.push(self.generate_uuid()),
&_ => {
// the process should never get to this point, because we are checking
// against valid templates in a previous step
eprintln!("'{}' is an invalid template", template);
}
}
}
codes
}
/// Generate a UUID key.
/// Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
/// Example: c17247f6-55b6-9226-28fc-952b7becc6e9
#[must_use]
#[allow(
clippy::unnecessary_cast,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
pub fn generate_uuid(&self) -> String {
let alphabet = String::from("abcdef0123456789");
let mut code = String::new();
for _ in 0..32 {
// 16 is the length of the above Hex alphabet
let number = rand::random::<f32>() * (16 as f32);
let number = number.round() as usize;
if let Some(character) = alphabet.chars().nth(number) {
code.push(character)
}
}
code.insert(20, '-');
code.insert(16, '-');
code.insert(12, '-');
code.insert(8, '-');
code
}
}

231
src/tera_lib.rs Normal file
View File

@ -0,0 +1,231 @@
/*! Utility functions for variety of tasks */
//
// Copyright 2020, Jesús Pérez Lorenzo
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use tera::{Tera};
/// make content from template path: from `root_path` + extension search for ".toml" or ".json"
/// template should be `root_path`.html and lang should be included in `root_path`
#[must_use]
#[allow(clippy::implicit_hasher)]
pub fn make_template_content(
root_path: &str,
ctx: &mut tera::Context,
data_hash: &mut HashMap<String, String>,
load_extend: bool,
) -> String {
let mut data_path = format!("{}.toml", &root_path);
if !Path::new(&data_path).exists() {
data_path = format!("{}.json", &root_path);
}
match hash_from_data(&data_path,ctx,data_hash,load_extend) {
Ok(_) => match data_templated(
"",
format!("{}.html", &root_path).as_str(),
ctx,
data_hash,
) {
Ok(templated_file) => return templated_file,
Err(e) => println!(
"Error generating template {}.html: {}",
&root_path, e
),
},
Err(e) => println!(
"Error with data template {} - {} : {}",
&root_path, &data_path, e
),
}
"".to_string()
}
/// `get_json_val` is a simple match Option to remove quotes in json values and returned as a String
#[must_use]
pub fn get_json_val(json_value: &serde_json::Value, dflt_val: String) -> String {
match json_value.as_str() {
Some(val) => val.to_string(),
None => dflt_val,
}
}
/// `get_toml_val` is a simple match Option to remove quotes in json values and returned as a String
#[must_use]
pub fn get_toml_val(toml_value: &toml::Value, dflt_val: String) -> String {
match toml_value.as_str() {
Some(val) => val.to_string(),
None => dflt_val,
}
}
/// `get_toml_val` is a simple match Option to remove quotes in json values and returned as a String
#[must_use]
pub fn get_yaml_val(yaml_value: &serde_yaml::Value, dflt_val: String) -> String {
match yaml_value.as_str() {
Some(val) => val.to_string(),
None => dflt_val,
}
}
/// Load a data file, fill a receive HASH, load template file and applied with Tera
/// By file extension is able to load data from JSON or TOML (as a table Value)
///
/// Unwrap lines in the following example has to be changed to "matched" or ? with -> Result<>
///```rust
/// let mut data_hash: HashMap<String, String> = HashMap::new();
/// let path = String::from("resources/mail/test.json".to_string());
/// let res = hash_from_data(&data_path, &mut data_hash).unwrap();
/// let root_path = String::from("resources/mail");
/// let tpl_result = data_templated(&root_path,"test.html",&res).unwrap();
/// ```
#[allow(clippy::missing_errors_doc,clippy::implicit_hasher)]
//
pub fn hash_from_data(path: &str, ctx: &mut tera::Context, data_hash: &mut HashMap<String, String>, load_extend: bool) -> anyhow::Result<()> {
// {HashMap<String, String>> {
let mut check_value = | key: String, v: String | {
if let Some(first_char) = v.chars().next() {
if first_char == '@' {
let field_file = &v[1..];
let _ = hash_from_data(field_file, ctx, data_hash, load_extend);
// dbg!("{}",&data_hash);
return;
}
}
data_hash.insert(key, v);
};
let vars_content = fs::read_to_string(&path)?;
//let mut data_hash: HashMap<String, String> = HashMap::new();
if let Some(extension) = path.split('.').last() {
//println!("File {} extension {}", &path, &extension);
match extension {
"json" => {
let data: HashMap<String, serde_json::Value> = serde_json::from_str(&vars_content)?;
if load_extend {
match tera::Context::from_serialize(data) {
Ok(res) => {
ctx.extend(res);
},
Err(e) => {
println!("Error loading {}: {}",&path, e);
},
}
} else {
for (key, value) in data {
let val = get_json_val(&value, "".into());
check_value(key,val);
}
}
}
"toml" => {
// As a table content
let data: toml::Value = toml::from_str(&vars_content)?;
// let data = &vars_content.parse::<toml::Value>()?;
if load_extend {
match tera::Context::from_serialize(data) {
Ok(res) => {
ctx.extend(res);
},
Err(e) => {
println!("Error loading {}: {}",&path, e);
},
}
} else if let Some(table_data) = data.as_table() {
for (key, value) in table_data {
println!("-{}-{}",&key,&value);
let val = get_toml_val(&value, "".into());
check_value(key.into(),val);
}
}
}
"yaml" => {
let data: HashMap<String, serde_yaml::Value> = serde_yaml::from_str(&vars_content)?;
if load_extend {
match tera::Context::from_serialize(data) {
Ok(res) => {
ctx.extend(res);
},
Err(e) => {
println!("Error loading {}: {}",&path, e);
},
}
} else {
for (key, value) in data {
let val = get_yaml_val(&value, "".into());
check_value(key,val);
}
}
}
_ => {
println!(
"File {} format extension {} not defined ",
&path, &extension
);
}
}
} else {
println!("File {} unknown extension", &path);
}
// dbg!(&data_hash);
Ok(()) // data_hash)
}
/// Data tempalted create a Tera context form hashmap and reads a file template from path/template_name
/// Finally render to a String
#[allow(clippy::missing_errors_doc, clippy::implicit_hasher)]
pub fn data_templated(
path: &str,
template_name: &str,
tpl_context: &mut tera::Context,
content: &HashMap<String, String>,
) -> anyhow::Result<String> {
// let mut tpl_context = Context::new();
for (key, value) in content.iter() {
tpl_context.insert(key, value);
}
let mut tera = Tera::default();
let template_head = "header.html";
let tpl = read_path_file(&path, &template_name, "content")?;
let mut all_tpls = vec![("data-template.html",tpl)];
match read_path_file(&path, &template_head, "content") {
Ok(tpl_head) => all_tpls.push((&template_head,tpl_head)),
Err(_) => {} // ignore if no header
}
tera.add_raw_templates(all_tpls)?;
let res = tera.render("data-template.html", &tpl_context)?;
Ok(res)
}
/// Read a `file_name` from a path (root / `file_name`) ( "" `file_name_full_path` )
/// format of reading can be matched with `file_type`
/// TODO implement other types to read
#[allow(clippy::missing_errors_doc)]
pub fn read_path_file(path: &str, file_name: &str, file_type: &str) -> anyhow::Result<String> {
let mut result = String::from("");
let mut root_path = format!("{}/", path);
if root_path.as_str() == "/" {
root_path = String::from("");
}
//println!("{}",format!("{}{}", &root_path.as_str(), &file_name));
match file_type {
"content" => {
result = fs::read_to_string(format!("{}{}", &root_path.as_str(), &file_name).as_str())?
}
_ => println!(
"File {}{} type {} not defined ",
&root_path, &file_name, &file_type
),
}
Ok(result)
}
/// `hash_content` to hash content with blake3:hash
///```rust
/// let texto = String::from("Esto es una prueba");
/// println!("{}",&texto);
/// println!("hash: {}",hash_content(&texto));
///```
#[must_use]
pub fn hash_content(content: &str) -> String {
let cntnt = content.as_bytes().to_vec(); //std::fmt::Binary(content.as_str());
// blake3::hash(b"foobarbaz");
let hash = blake3::hash(&cntnt);
format!("{}", hash.to_hex())
}

123
src/utils.rs Normal file
View File

@ -0,0 +1,123 @@
/*! Utility functions for variety of tasks */
//
// Copyright 2020, Jesús Pérez Lorenzo
use anyhow::anyhow;
use regex::Regex;
use std::fs;
use std::process::Command;
#[derive(PartialEq, Default, Clone, Debug)]
struct Commit {
hash: String,
message: String,
}
/// A very simple Operating System Command runner capturing output
/// Allows two arguments, they can be empty
///```rust
/// let res =
/// match run_command("pwd", "", "") {
/// Ok(_) => format!("{} {}", "pwd","Done"),
/// Err(e) => format!("{}",e),
/// };
/// println!("Result: {}",res);
/// ```
#[allow(dead_code)]
#[allow(clippy::missing_errors_doc, clippy::filter_map)]
pub fn run_command(cmd: &str, arg1: &str, arg2: &str) -> anyhow::Result<()> {
let output = match arg2 {
"" => match arg1 {
"" => Command::new(cmd).output()?,
_ => Command::new(cmd).arg(arg1).output()?,
},
_ => Command::new(cmd).arg(arg1).arg(arg2).output()?,
};
if !output.status.success() {
//dbg!(&output);
let code = match output.status.code() {
Some(val) => val,
None => -1,
};
let stderr = String::from_utf8(output.stderr)?;
let msg = format!(
"Command {} executed with failing errors({}): {}",
&cmd, &code, &stderr
);
println!("{}", &msg);
return Err(anyhow!(msg));
// error_chain::bail!("Command {} executed with failing error code", &cmd);
}
let pattern = Regex::new(
r"(?x)
([0-9a-fA-F]+) # commit hash
(.*) # The commit message",
)?;
String::from_utf8(output.stdout)?
.lines()
.filter_map(|line| pattern.captures(line))
.map(|cap| Commit {
hash: cap[1].to_string(),
message: cap[2].trim().to_string(),
})
.take(5)
.for_each(|x| println!("{:?}", x));
Ok(())
}
/// Trim newline
/// this function is critical for base64 content as usually adds a newline at the end, causing decode not work properly with code.
/// `trim_newline` should be called to clean content before to be decoded.
pub fn trim_newline(s: &mut String) {
if s.ends_with('\n') {
s.pop();
if s.ends_with('\r') {
s.pop();
}
}
}
/// Get last char from String
#[must_use]
pub fn get_last_char_num(text: &str) -> u32 {
let v_last: Vec<char> = text.chars().rev().take(1).collect();
let s_last: String = v_last.iter().collect();
let mut num = 0;
match s_last.parse::<u32>() {
Ok(n) => num = n,
Err(e) => println!("Error last_char_num: {}", e),
}
num
}
/// Check file permissions
/// Initial idea was capture file mode as 600 but permissions for unix in rust something else has to be done
/// ```rust
/// #[cfg(target_family = "unix")]
/// use std::os::unix::fs::PermissionsExt
/// ```
#[allow(clippy::missing_errors_doc)]
pub fn check_file_permissions(path: &str) -> anyhow::Result<bool> {
// let info = os_info::get();
// // Print full information:
// println!("OS information: {}", info);
// // Print information separately:
// println!("Type: {}", info.os_type());
// println!("Version: {}", info.version());
// println!("Bitness: {}", info.bitness());
// let os = os_type::current_platform();
// let macos = os_type::OSType::OSX;
// match os_type::current_platform().os_type {
// os_type::OSType::OSX => Ok(true),
// _ => {
// let perm = fs::File::open(path)?.metadata()?.permissions();
// Ok(perm.readonly())
// }
// }
let perm = fs::File::open(path)?.metadata()?.permissions();
// if ! perm.readonly() {
// println!("Review permissions: {}", path);
// }
// Ok(true)
Ok(perm.readonly())
}