use std::{fmt, time, thread};
use std::fs::File;
use std::io::{Read, Write};
use std::collections::HashMap;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT};
use serde_json::json;
use serde::Serialize;
use serde_derive::Serialize;
use crate::response::Response;
use crate::error::{Error, Result};
#[derive(Serialize)]
pub struct Client {
server: String,
port: u16,
certificate: String,
accept_invalid_certs: bool,
proxy: String,
connect_timeout: time::Duration,
session_timeout: u64,
domain: String,
read_only: bool,
continue_last_session: bool,
sid: String,
uid: String,
api_server_version: String,
wait_for_task: bool,
log_file: String,
all_calls: Vec<serde_json::Value>,
show_password: bool,
}
impl Client {
pub fn new(server: &str, port: u16) -> Self {
Client {
server: String::from(server),
port,
certificate: String::new(),
accept_invalid_certs: false,
proxy: String::new(),
connect_timeout: time::Duration::from_secs(30),
session_timeout: 600,
domain: String::new(),
read_only: false,
continue_last_session: false,
sid: String::with_capacity(50),
uid: String::with_capacity(40),
api_server_version: String::with_capacity(5),
wait_for_task: true,
log_file: String::new(),
all_calls: Vec::new(),
show_password: false,
}
}
pub fn login(&mut self, user: &str, pass: &str) -> Result<Response> {
let payload = json!({
"user": user,
"password": pass,
"domain": self.domain,
"session-timeout": self.session_timeout,
"read-only": self.read_only,
"continue-last-session": self.continue_last_session,
});
let login = self.call("login", payload)?;
if login.is_success() {
self.sid = match login.data["sid"].as_str() {
Some(t) => t,
None => return Err(Error::InvalidResponse("sid", json!(login)))
}.to_string();
self.api_server_version = match login.data["api-server-version"].as_str() {
Some(t) => t,
None => return Err(Error::InvalidResponse("api-server-version", json!(login)))
}.to_string();
if !self.read_only {
self.uid = match login.data["uid"].as_str() {
Some(t) => t,
None => return Err(Error::InvalidResponse("uid", json!(login)))
}.to_string();
}
}
Ok(login)
}
pub fn logout(&mut self) -> Result<Response> {
let logout = self.call("logout", json!({}))?;
if logout.is_success() {
self.sid.clear();
self.uid.clear();
self.api_server_version.clear();
}
Ok(logout)
}
pub fn call(&mut self, command: &str, payload: serde_json::Value) -> Result<Response> {
let url = format!("https://{}:{}/web_api/{}", self.server, self.port, command);
let headers = self.headers()?;
let headers2 = headers.clone();
let reqwest_client = self.build_client(headers)?;
let mut reqwest_response = reqwest_client.post(url.as_str())
.json(&payload)
.send()?;
let mut res = Response::set(&mut reqwest_response)?;
if res.data.get("task-id").is_some() && self.wait_for_task == true {
res = self._wait_for_task(res.data["task-id"].as_str().unwrap(), command)?;
}
if !self.log_file.is_empty() {
self.update_calls(command, url.as_str(), headers2, payload, &res)?;
}
Ok(res)
}
fn headers(&self) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(USER_AGENT, HeaderValue::from_static("cp_api"));
if !self.sid.is_empty() {
let n = HeaderName::from_static("x-chkp-sid");
let v = HeaderValue::from_str(self.sid.as_str())?;
headers.insert(n, v);
}
Ok(headers)
}
fn build_client(&self, headers: HeaderMap) -> Result<reqwest::Client> {
let mut builder = reqwest::ClientBuilder::new();
builder = builder.default_headers(headers);
builder = builder.timeout(self.connect_timeout);
if !self.proxy.is_empty() {
builder = builder.proxy(reqwest::Proxy::https(self.proxy.as_str())?);
}
if self.accept_invalid_certs == true && self.certificate.is_empty() {
builder = builder.danger_accept_invalid_certs(true);
}
if !self.certificate.is_empty() {
let mut buf: Vec<u8> = Vec::new();
File::open(self.certificate.as_str())?
.read_to_end(&mut buf)?;
let cert = reqwest::Certificate::from_der(&buf)?;
builder = builder.add_root_certificate(cert);
builder = builder.danger_accept_invalid_certs(false);
}
let client = builder.build()?;
Ok(client)
}
pub fn query(&mut self, command: &str, details_level: &str) -> Result<Response> {
let mut res = Response::new();
let mut vec: Vec<serde_json::Value> = Vec::new();
let limit = 50;
let mut offset = 0;
let mut to = 0;
let mut total = 1;
while to != total {
let payload = json!({
"details-level": details_level,
"limit": limit,
"offset": offset
});
res = self.call(command, payload)?;
if res.is_not_success() {
let msg = format!("Received an unsuccessful Response from the API \
while running a query. Error code: {}, message: {}",
res.data["code"], res.data["message"]);
return Err(Error::Custom(msg));
}
to = match res.data["to"].as_u64() {
Some(t) => t,
None => return Err(Error::InvalidResponse("to", json!(res)))
};
total = match res.data["total"].as_u64() {
Some(t) => t,
None => return Err(Error::InvalidResponse("total", json!(res)))
};
let mut objects = match res.data["objects"].as_array_mut() {
Some(t) => t,
None => return Err(Error::InvalidResponse("objects", json!(res)))
};
vec.append(&mut objects);
offset += 50;
}
res.objects = vec;
res.data = json!({});
Ok(res)
}
pub fn certificate(&mut self, s: &str) {
self.certificate = s.to_string();
}
pub fn accept_invalid_certs(&mut self, b: bool) {
self.accept_invalid_certs = b;
}
pub fn proxy(&mut self, s: &str) {
self.proxy = s.to_string();
}
pub fn connect_timeout(&mut self, t: u64) {
self.connect_timeout = time::Duration::from_secs(t);
}
pub fn session_timeout(&mut self, t: u64) {
self.session_timeout = t;
}
pub fn domain(&mut self, s: &str) {
self.domain = s.to_string();
}
pub fn read_only(&mut self, b: bool) {
self.read_only = b;
}
pub fn continue_last_session(&mut self, b: bool) {
self.continue_last_session = b;
}
pub fn sid(&self) -> &str {
self.sid.as_str()
}
pub fn uid(&self) -> &str {
self.uid.as_str()
}
pub fn api_server_version(&self) -> &str {
self.api_server_version.as_str()
}
pub fn wait_for_task(&mut self, b: bool) {
self.wait_for_task = b;
}
fn _wait_for_task(&mut self, taskid: &str, command: &str) -> Result<Response> {
let mut _res = Response::new();
loop {
_res = self.call("show-task", json!({"task-id": taskid, "details-level": "full"}))?;
let percent = match _res.data["tasks"][0].get("progress-percentage") {
Some(t) => t,
None => return Err(Error::InvalidResponse("progress-percentage", json!(_res)))
};
let status = match _res.data["tasks"][0].get("status") {
Some(t) => t,
None => return Err(Error::InvalidResponse("status", json!(_res)))
};
println!("{} {} - {}%", command, status, percent);
if status != "in progress" {
break;
}
thread::sleep(time::Duration::from_secs(5));
}
Ok(_res)
}
pub fn log_file(&mut self, s: &str) {
self.log_file = s.to_string();
}
fn update_calls(
&mut self,
command: &str,
url: &str,
headers: HeaderMap,
mut payload: serde_json::Value,
res: &Response,
) -> Result<()>
{
if command == "login" && self.show_password == false {
if let Some(obj) = payload.get_mut("password") {
*obj = json!("*****");
}
else {
let msg = String::from("Failed to get the password to obfuscate from payload");
return Err(Error::Custom(msg));
}
}
let mut map = HashMap::new();
for (k, v) in headers.iter() {
let k = k.as_str().to_string();
let v = v.to_str()?;
let v = v.to_string();
map.insert(k, v);
}
let j = json!({
"Request": {
"headers": map,
"payload": payload,
"url": url
},
"Response": res
});
let mut v = vec!(j);
self.all_calls.append(&mut v);
Ok(())
}
pub fn save_log(&mut self) -> Result<()> {
if self.log_file.is_empty() {
let msg = String::from("log_file on the Client is not set");
return Err(Error::Custom(msg));
}
let mut f = File::create(self.log_file.as_str())?;
let buf = Vec::new();
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut ser = serde_json::Serializer::with_formatter(buf, formatter);
self.all_calls.serialize(&mut ser)?;
f.write_all(&ser.into_inner())?;
self.all_calls.clear();
self.log_file.clear();
Ok(())
}
pub fn show_password(&mut self, b: bool) {
self.show_password = b;
}
}
impl Drop for Client {
fn drop(&mut self) {
if !self.sid.is_empty() {
if let Err(e) = self.logout() {
eprintln!("Error logging out while dropping the Client: {}", e);
}
}
}
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Client")
.field("server", &self.server)
.field("port", &self.port)
.field("certificate", &self.certificate)
.field("accept_invalid_certs", &self.accept_invalid_certs)
.field("proxy", &self.proxy)
.field("connect_timeout", &self.connect_timeout)
.field("session_timeout", &self.session_timeout)
.field("domain", &self.domain)
.field("read_only", &self.read_only)
.field("continue_last_session", &self.continue_last_session)
.field("sid", &self.sid)
.field("uid", &self.uid)
.field("api_server_version", &self.api_server_version)
.field("wait_for_task", &self.wait_for_task)
.field("log_file", &self.log_file)
.field("show_password", &self.show_password)
.finish()
}
}