use log::debug;
use reqwest::blocking::{Client, Response};
use reqwest::header::HeaderMap;
use std::time::Duration;
extern crate serde_json;
pub fn configure_logging() {
if cfg!(debug_assertions) {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
} else {
env_logger::builder()
.filter_level(log::LevelFilter::Off)
.init();
}
}
#[derive(Debug)]
enum APIError {
UnknownServerError,
InternalError,
InvalidHostname,
InvalidPort,
PortConflict,
InvalidDataLimit,
AccessKeyInexistent,
InvalidName,
InvalidRequest,
UnknownError,
}
const NAME_ENDPOINT: &str = "/name";
const SERVER_ENDPOINT: &str = "/server";
const HOSTNAME_ENDPOINT: &str = "/server/hostname-for-access-keys";
const CHANGE_PORT_ENDPOINT: &str = "/server/port-for-new-access-keys";
const KEY_DATA_LIMIT_ENDPOINT: &str = "/server/access-key-data-limit";
const METRICS_ENDPOINT: &str = "/metrics";
const ACCESS_KEYS_ENDPOINT: &str = "/access-keys";
impl std::fmt::Display for APIError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
APIError::UnknownServerError => write!(f, "An unknown server error occurred."),
APIError::InternalError => write!(f, "An internal error occurred."),
APIError::InvalidHostname => write!(f, "An invalid hostname or IP address was provided."),
APIError::InvalidPort => write!(f, "The requested port wasn't an integer from 1 through 65535, or the request had no port parameter."),
APIError::PortConflict => write!(f, "The requested port was already in use by another service."),
APIError::InvalidDataLimit => write!(f, "Invalid data limit."),
APIError::AccessKeyInexistent => write!(f, "Access key inexistent."),
APIError::InvalidName => write!(f, "Invalid name."),
APIError::InvalidRequest => write!(f, "Invalid request."),
APIError::UnknownError => write!(f, "An unknown error occurred."),
}
}
}
fn handle_json_api_result(response: Response) -> Result<serde_json::Value, String> {
match response.status() {
reqwest::StatusCode::OK => {
let response_body = response
.text()
.map_err(|_| "Error reading response body".to_string())?;
let json_value: serde_json::Value = serde_json::from_str(&response_body)
.map_err(|_| "Error deserializing JSON".to_string())?;
Ok(json_value)
}
reqwest::StatusCode::INTERNAL_SERVER_ERROR => Err(APIError::InternalError.to_string()),
_ => Err(APIError::UnknownError.to_string()),
}
}
fn handle_response_status(response: &Response, api_path: &str) -> Result<(), String> {
match response.status() {
reqwest::StatusCode::OK => Ok(()),
reqwest::StatusCode::NO_CONTENT => Ok(()),
reqwest::StatusCode::BAD_REQUEST => match api_path {
NAME_ENDPOINT => Err(APIError::InvalidName.to_string()),
HOSTNAME_ENDPOINT => Err(APIError::InvalidHostname.to_string()),
CHANGE_PORT_ENDPOINT => Err(APIError::InvalidPort.to_string()),
KEY_DATA_LIMIT_ENDPOINT | ACCESS_KEYS_ENDPOINT => {
Err(APIError::InvalidDataLimit.to_string())
}
_ => Err(APIError::InvalidRequest.to_string()),
},
reqwest::StatusCode::CONFLICT => Err(APIError::PortConflict.to_string()),
reqwest::StatusCode::NOT_FOUND => Err(APIError::AccessKeyInexistent.to_string()),
reqwest::StatusCode::INTERNAL_SERVER_ERROR => Err(APIError::InternalError.to_string()),
_ => Err(APIError::UnknownError.to_string()),
}
}
pub struct OutlineVPN<'a> {
api_url: &'a str,
session: Client,
request_timeout_in_sec: Duration,
}
impl OutlineVPN<'_> {
fn call_api(
&self,
api_path: &str,
request_method: reqwest::Method,
request_body: String,
) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.api_url, api_path);
debug!("URL: {}", url);
debug!("Method: {:?}", request_method);
debug!("Request Body: {}", request_body);
let response = self
.session
.request(request_method, &url)
.timeout(self.request_timeout_in_sec)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(request_body)
.send()?;
Ok(response)
}
pub fn get_server_info(&self) -> Result<serde_json::Value, String> {
let response = match self.call_api(SERVER_ENDPOINT, reqwest::Method::GET, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_json_api_result(response)
}
pub fn change_hostname_for_access_keys(&self, hostname: &str) -> Result<(), String> {
let body = format!(r#"{{ "hostname": "{}" }}"#, hostname);
let response = match self.call_api(HOSTNAME_ENDPOINT, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, HOSTNAME_ENDPOINT)
}
pub fn change_default_port_for_newly_created_access(&self, port: &str) -> Result<(), String> {
let body = format!(r#"{{ "port": {} }}"#, port);
let response = match self.call_api(CHANGE_PORT_ENDPOINT, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, CHANGE_PORT_ENDPOINT)
}
pub fn set_data_transfer_limit_for_all_access_keys(&self, byte: &u64) -> Result<(), String> {
let body = format!(r#"{{ "limit": {{ "bytes": {} }} }}"#, byte);
let response = match self.call_api(KEY_DATA_LIMIT_ENDPOINT, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, KEY_DATA_LIMIT_ENDPOINT)
}
pub fn remove_data_limit_for_all_access_keys(&self) -> Result<(), String> {
let response = match self.call_api(
KEY_DATA_LIMIT_ENDPOINT,
reqwest::Method::DELETE,
String::new(),
) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, KEY_DATA_LIMIT_ENDPOINT)
}
pub fn rename_server(&self, name: &str) -> Result<(), String> {
let body = format!(r#"{{ "name": "{}" }}"#, name);
let response = match self.call_api(NAME_ENDPOINT, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, NAME_ENDPOINT)
}
pub fn create_access_key(&self) -> Result<serde_json::Value, String> {
let response =
match self.call_api(ACCESS_KEYS_ENDPOINT, reqwest::Method::POST, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_json_api_result(response)
}
pub fn list_access_keys(&self) -> Result<serde_json::Value, String> {
let response =
match self.call_api(ACCESS_KEYS_ENDPOINT, reqwest::Method::GET, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_json_api_result(response)
}
pub fn delete_access_key_by_id(&self, id: &u16) -> Result<(), String> {
let api_path = format!("{}/{}", ACCESS_KEYS_ENDPOINT, id);
let response = match self.call_api(&api_path, reqwest::Method::DELETE, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, ACCESS_KEYS_ENDPOINT)
}
pub fn change_name_for_access_key(&self, id: &u16, username: &str) -> Result<(), String> {
let body = format!(r#"{{ "name": "{}" }}"#, username);
let api_path = format!("{}/{}/name", ACCESS_KEYS_ENDPOINT, id);
let response = match self.call_api(&api_path, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, ACCESS_KEYS_ENDPOINT)
}
pub fn set_data_transfer_limit_by_id(&self, id: &u16, byte: &u64) -> Result<(), String> {
let body = format!(r#"{{ "limit": {{ "bytes": {} }} }}"#, byte);
let api_path = format!("{}/{}/data-limit", ACCESS_KEYS_ENDPOINT, id);
let response = match self.call_api(&api_path, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, ACCESS_KEYS_ENDPOINT)
}
pub fn del_data_transfer_limit_by_id(&self, id: &u16) -> Result<(), String> {
let api_path = format!("{}/{}/data-limit", ACCESS_KEYS_ENDPOINT, id);
let response = match self.call_api(&api_path, reqwest::Method::DELETE, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, ACCESS_KEYS_ENDPOINT)
}
pub fn get_each_access_key_data_transferred(&self) -> Result<serde_json::Value, String> {
let api_path = format!("{}/transfer", METRICS_ENDPOINT);
let response = match self.call_api(&api_path, reqwest::Method::GET, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_json_api_result(response)
}
pub fn get_whether_metrics_is_being_shared(&self) -> Result<serde_json::Value, String> {
let api_path = format!("{}/enabled", METRICS_ENDPOINT);
let response = match self.call_api(&api_path, reqwest::Method::GET, String::new()) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_json_api_result(response)
}
pub fn enable_or_disable_sharing_metrics(&self, metrics_enabled: bool) -> Result<(), String> {
let body = format!(r#"{{ "metricsEnabled": {} }}"#, metrics_enabled);
let api_path = format!("{}/enabled", METRICS_ENDPOINT);
let response = match self.call_api(&api_path, reqwest::Method::PUT, body) {
Ok(response) => response,
Err(_) => return Err(APIError::UnknownServerError.to_string()),
};
handle_response_status(&response, METRICS_ENDPOINT)
}
}
pub fn new<'a>(
cert_sha256: &'a str,
api_url: &'a str,
request_timeout: Duration,
) -> OutlineVPN<'a> {
let mut headers = HeaderMap::new();
headers.insert(
"Certificate-SHA256",
reqwest::header::HeaderValue::from_str(cert_sha256).unwrap(),
);
let session = Client::builder()
.danger_accept_invalid_certs(true)
.default_headers(headers)
.build()
.unwrap();
OutlineVPN {
api_url,
session,
request_timeout_in_sec: request_timeout,
}
}