use crypto::digest::Digest;
use crypto::sha2::Sha512;
use crypto::hmac::Hmac;
use crypto::mac::Mac;
use json::JsonValue;
use rpassword::prompt_password_stdout;
use std::fmt;
use std::io::Read;
use std::fs::OpenOptions;
use std::collections::HashMap;
use reqwest::Url;
use reqwest::header::{USER_AGENT, CONTENT_TYPE};
use reqwest::Method;
const VERSION :&str = "1.7";
pub const URL: &str = "https://blih.epitech.eu";
pub struct Blih {
pub user_agent: String,
pub url: String,
pub user: Option<String>,
pub token: Option<String>,
}
impl Blih {
pub fn new(user: Option<&str>, token: Option<&str>, url: Option<&str>) -> Blih {
let user = match user {
Some(s) => Some(s.to_string()),
None => None,
};
let token = match token {
Some(s) => Some(s.to_string()),
None => None,
};
let url = match url {
Some(s) => String::from(s),
None => String::from("https://blih.epitech.eu"),
};
let user_agent = String::from("blih-".to_owned() + VERSION);
Blih {
user_agent,
url,
user,
token,
}
}
pub fn ask_password(&mut self) -> Result<(), BlihErr> {
match prompt_password_stdout("Password: ") {
Ok(s) => {
let mut hash = Sha512::new();
hash.input_str(&s);
self.token = Some(hash.result_str());
},
Err(_) => self.token = None,
};
Ok(())
}
fn sign_token(&self, data: &Option<JsonValue>) -> Result<String, BlihErr> {
let mut hmac = Hmac::new(Sha512::new(), match &self.token {
Some(s) => s.as_bytes(),
None => return Err(BlihErr::NoTokenProvided),
});
hmac.input(match &self.user {
Some(s) => s.as_bytes(),
None => return Err(BlihErr::NoUserNameProvided),
});
if data.is_some() {
hmac.input(data.as_ref().unwrap().pretty(4).as_bytes());
}
Ok(hex::encode(hmac.result().code()))
}
pub fn whoami(&self) -> Result<String, BlihErr> {
self.request("/whoami", Method::GET, None)
}
pub fn list_repo(&self) -> Result<String, BlihErr> {
self.request("/repositories", Method::GET, None)
}
pub fn info_repo(&self, name: &str) -> Result<String, BlihErr> {
self.request(&("/repository/".to_owned() + name), Method::GET, None)
}
pub fn delete_repo(&self, name: &str) -> Result<String, BlihErr> {
self.request(&("/repository/".to_owned() + name), Method::DELETE, None)
}
pub fn create_repo(&self, name: &str) -> Result<String, BlihErr> {
let mut map = HashMap::new();
map.insert("name", name);
map.insert("type", "git");
self.request("/repositories", Method::POST, Some(JsonValue::from(map)))
}
pub fn get_acl(&self, name: &str) -> Result<String, BlihErr> {
self.request(&("/repository/".to_owned() + name + "/acls"), Method::GET, None)
}
pub fn set_acl(&self, name: &str, user: &str, acl: &str) -> Result<String, BlihErr> {
let mut map = HashMap::new();
map.insert("acl", acl);
map.insert("user", user);
self.request(&("/repository/".to_owned() + name + "/acls"), Method::POST, Some(JsonValue::from(map)))
}
pub fn list_key(&self) -> Result<String, BlihErr> {
self.request("/sshkeys", Method::GET, None)
}
pub fn upload_key_str(&self, key: &str) -> Result<String, BlihErr> {
let mut map = HashMap::new();
map.insert("sshkey", key);
self.request("/sshkey", Method::POST, Some(JsonValue::from(map)))
}
pub fn upload_key_path(&self, key: &str) -> Result<String, BlihErr> {
let mut file = match OpenOptions::new().read(true).open(key) {
Ok(s) => s,
Err(_) => return Err(BlihErr::InvalidSshKey),
};
let mut key = String::new();
match file.read_to_string(&mut key) {
Ok(_) => (),
Err(_) => return Err(BlihErr::InvalidSshKey),
}
key = key.trim_matches('\n').to_string();
self.upload_key_str(&key)
}
fn request(&self, path: &str, meth: Method, data: Option<JsonValue>) -> Result<String, BlihErr> {
let mut map = JsonValue::new_object();
let token = self.sign_token(&data);
map.insert("user", match &self.user {
Some(s) => s.as_str(),
None => return Err(BlihErr::NoUserNameProvided),
}).unwrap();
map.insert("signature", match &token {
Ok(s) => s.as_str(),
Err(_) => return Err(BlihErr::NoTokenProvided),
}).unwrap();
if data.is_some() {
map.insert("data", data.unwrap()).unwrap();
}
let uri = match Url::parse((self.url.clone() + path).as_str()) {
Ok(o) => o,
Err(_) => return Err(BlihErr::InvalidUrl),
};
let client = reqwest::blocking::Client::new().request(meth, uri)
.header(USER_AGENT, &self.user_agent)
.header(CONTENT_TYPE, "application/json")
.body(map.dump()).send();
let client = match client {
Ok(o) => o,
Err(_) => return Err(BlihErr::RequestFailed),
};
Ok(client.text().unwrap())
}
}
#[derive(Debug, PartialEq)]
pub enum BlihErr {
InvalidRequest,
InvalidUrl,
RequestFailed,
NoTokenProvided,
NoUserNameProvided,
InvalidSshKey,
HeaderError,
}
impl std::error::Error for BlihErr {}
impl fmt::Display for BlihErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
BlihErr::InvalidRequest => write!(f, "Invalid request"),
BlihErr::InvalidUrl => write!(f, "Invalid Url"),
BlihErr::RequestFailed => write!(f, "Request Failed"),
BlihErr::NoTokenProvided => write!(f, "No token was provided"),
BlihErr::NoUserNameProvided => write!(f, "No username was provided"),
BlihErr::InvalidSshKey => write!(f, "Invalid sshkey file"),
BlihErr::HeaderError => write!(f, "Error while building header"),
}
}
}