use chrono::{DateTime, Utc};
use openssl::hash::MessageDigest;
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::sign::Signer;
use reqwest::{header::HeaderMap, Response};
use serde_json::json;
use sha2::{Digest, Sha256};
use std::fs;
pub struct AuthConfig {
user: String,
fingerprint: String,
tenancy: String,
region: String,
keypair: PKey<Private>,
}
impl AuthConfig {
pub fn new(
user: String,
key_file: String,
fingerprint: String,
tenancy: String,
region: String,
passphrase: String,
) -> AuthConfig {
let key = fs::read_to_string(&key_file).expect("file doest not exists");
let keypair =
Rsa::private_key_from_pem_passphrase(key.as_bytes(), passphrase.as_bytes()).unwrap();
let keypair = PKey::from_rsa(keypair).unwrap();
return AuthConfig {
user,
fingerprint,
tenancy,
region,
keypair,
};
}
}
pub struct QueryDetails {
pub compartment_id: String,
pub statement: String,
}
pub struct Nosql {
config: AuthConfig,
service_endpoint: String,
}
pub struct TableLimits {
pub max_read_units: u16,
pub max_write_units: u16,
pub max_storage_in_g_bs: u16,
}
pub struct CreateTableDetails {
pub name: String,
pub compartment_id: String,
pub ddl_statement: String,
pub table_limits: TableLimits,
}
fn encode_body(body: &String) -> String {
let mut hasher = Sha256::new();
hasher.update(body);
let result = hasher.finalize();
let b64 = base64::encode(result);
return b64;
}
fn oci_signer(
config: &AuthConfig,
headers: &mut HeaderMap,
method: String,
path: &String,
host: &String,
) {
let date = headers.get("date").unwrap();
let date = date.to_str().unwrap();
let host = host.replace("http://", "").replace("https://", "");
let mut data = format!(
"date: {}\n(request-target): {} {}\nhost: {}",
date, method, path, host
);
let mut headers_auth = String::from("date (request-target) host");
if headers.contains_key("content-length") {
let content_length = headers.get("content-length").unwrap();
let content_length = content_length.to_str().unwrap();
data = format!("{}\ncontent-length: {}", data, content_length);
headers_auth = format!("{} content-length", headers_auth)
}
if headers.contains_key("content-type") {
let content_type = headers.get("content-type").unwrap();
let content_type = content_type.to_str().unwrap();
data = format!("{}\ncontent-type: {}", data, content_type);
headers_auth = format!("{} content-type", headers_auth)
}
if headers.contains_key("x-content-sha256") {
let content_sha256 = headers.get("x-content-sha256").unwrap();
let content_sha256 = content_sha256.to_str().unwrap();
data = format!("{}\nx-content-sha256: {}", data, content_sha256);
headers_auth = format!("{} x-content-sha256", headers_auth)
}
let mut signer = Signer::new(MessageDigest::sha256(), &config.keypair).unwrap();
signer.update(data.as_bytes()).unwrap();
let signature = signer.sign_to_vec().unwrap();
let b64 = base64::encode(signature);
let key_id = format!("{}/{}/{}", config.tenancy, config.user, config.fingerprint);
let authorization = format!("Signature algorithm=\"rsa-sha256\",headers=\"{}\",keyId=\"{}\",signature=\"{}\",version=\"1\"",headers_auth,key_id,b64);
headers.insert("authorization", authorization.parse().unwrap());
}
impl Nosql {
pub fn new(config: AuthConfig, service_endpoint: Option<String>) -> Nosql {
let se =
service_endpoint.unwrap_or(format!("https://nosql.{}.oraclecloud.com", config.region));
return Nosql {
config,
service_endpoint: se,
};
}
pub async fn create_table(
&self,
create_table_detais: CreateTableDetails,
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
let body_json = json!({
"name": &create_table_detais.name,
"compartmentId": &create_table_detais.compartment_id,
"ddlStatement": &create_table_detais.ddl_statement,
"tableLimits": {
"maxReadUnits": create_table_detais.table_limits.max_read_units,
"maxWriteUnits": create_table_detais.table_limits.max_write_units,
"maxStorageInGBs": create_table_detais.table_limits.max_storage_in_g_bs
}
});
let body = body_json.to_string();
let now: DateTime<Utc> = Utc::now();
headers.insert(
"date",
now.to_rfc2822().replace("+0000", "GMT").parse().unwrap(),
);
headers.insert("x-content-sha256", encode_body(&body).parse().unwrap());
headers.insert("content-length", body.len().to_string().parse().unwrap());
headers.insert(
"content-type",
String::from("application/json").parse().unwrap(),
);
let path = format!("/20190828/tables");
oci_signer(
&self.config,
&mut headers,
String::from("post"),
&path,
&self.service_endpoint,
);
let response = client
.post(format!("{}{}", self.service_endpoint, path))
.body(body)
.headers(headers)
.send()
.await?;
return Ok(response);
}
pub async fn query(
&self,
query_details: QueryDetails,
limit: u16,
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
let body_json = json!({
"compartmentId": &query_details.compartment_id,
"statement": &query_details.statement,
});
let body = body_json.to_string();
let now: DateTime<Utc> = Utc::now();
headers.insert(
"date",
now.to_rfc2822().replace("+0000", "GMT").parse().unwrap(),
);
headers.insert("x-content-sha256", encode_body(&body).parse().unwrap());
headers.insert("content-length", body.len().to_string().parse().unwrap());
headers.insert(
"content-type",
String::from("application/json").parse().unwrap(),
);
let path = format!("/20190828/query?limit={}", limit);
oci_signer(
&self.config,
&mut headers,
String::from("post"),
&path,
&self.service_endpoint,
);
let response = client
.post(format!("{}{}", self.service_endpoint, path))
.body(body)
.headers(headers)
.send()
.await?;
return Ok(response);
}
}