use crate::{
CryptoError, Entry, IndexedStorer, IndexedTypeStorer, StorableType, Storer, TypeStorer,
};
use async_trait::async_trait;
use mongodb::bson::Document;
use once_cell::sync::Lazy;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::{
error::Error,
fmt::{self, Display, Formatter},
fs::File,
io::Read,
sync::{Arc, RwLock},
};
static CLIENT_TLS_CONFIG: Lazy<RwLock<Arc<Option<ClientTlsConfig>>>> =
Lazy::new(|| RwLock::new(Default::default()));
#[derive(Debug)]
pub enum RedactStorerError {
InternalError {
source: Box<dyn Error + Send + Sync>,
},
NotFound,
Pkcs12FileNotReadable { source: std::io::Error },
ServerCaCertFileNotReadable { source: std::io::Error },
HttpClientNotBuildable { source: reqwest::Error },
}
impl Error for RedactStorerError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
RedactStorerError::InternalError { ref source } => Some(source.as_ref()),
RedactStorerError::NotFound => None,
RedactStorerError::Pkcs12FileNotReadable { ref source } => Some(source),
RedactStorerError::HttpClientNotBuildable { ref source } => Some(source),
RedactStorerError::ServerCaCertFileNotReadable { ref source } => Some(source),
}
}
}
impl Display for RedactStorerError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
RedactStorerError::InternalError { .. } => {
write!(f, "Internal error occurred")
}
RedactStorerError::NotFound => {
write!(f, "Requested document not found")
}
RedactStorerError::Pkcs12FileNotReadable { .. } => {
write!(f, "Could not open PKCS12 client TLS file")
}
RedactStorerError::HttpClientNotBuildable { .. } => {
write!(f, "Could not build HTTP request client")
}
RedactStorerError::ServerCaCertFileNotReadable { .. } => {
write!(f, "Could not read server CA certificate")
}
}
}
}
impl From<RedactStorerError> for CryptoError {
fn from(rse: RedactStorerError) -> Self {
match rse {
RedactStorerError::InternalError { .. } => CryptoError::InternalError {
source: Box::new(rse),
},
RedactStorerError::NotFound => CryptoError::NotFound {
source: Box::new(rse),
},
RedactStorerError::Pkcs12FileNotReadable { .. } => CryptoError::InternalError {
source: Box::new(rse),
},
RedactStorerError::HttpClientNotBuildable { .. } => CryptoError::InternalError {
source: Box::new(rse),
},
RedactStorerError::ServerCaCertFileNotReadable { .. } => CryptoError::InternalError {
source: Box::new(rse),
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ClientTlsConfig {
pub pkcs12_path: String,
pub server_ca_path: Option<String>,
}
impl ClientTlsConfig {
pub fn current() -> Arc<Option<ClientTlsConfig>> {
CLIENT_TLS_CONFIG.read().unwrap().clone()
}
pub fn make_current(self) {
*CLIENT_TLS_CONFIG.write().unwrap() = Arc::new(Some(self))
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RedactStorer {
url: String,
}
impl RedactStorer {
pub fn new(url: &str) -> Self {
Self {
url: url.to_owned(),
}
}
}
impl From<RedactStorer> for IndexedTypeStorer {
fn from(rs: RedactStorer) -> Self {
IndexedTypeStorer::Redact(rs)
}
}
impl From<RedactStorer> for TypeStorer {
fn from(rs: RedactStorer) -> Self {
TypeStorer::Indexed(IndexedTypeStorer::Redact(rs))
}
}
impl RedactStorer {
fn get_http_client() -> Result<reqwest::Client, RedactStorerError> {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONNECTION,
reqwest::header::HeaderValue::from_static("close"),
);
match *ClientTlsConfig::current() {
Some(ref ctc) => {
let mut pkcs12_vec: Vec<u8> = vec![];
File::open(&ctc.pkcs12_path)
.map_err(|source| RedactStorerError::Pkcs12FileNotReadable { source })?
.read_to_end(&mut pkcs12_vec)
.map_err(|source| RedactStorerError::Pkcs12FileNotReadable { source })?;
let pkcs12 = reqwest::Identity::from_pem(&pkcs12_vec)
.map_err(|source| RedactStorerError::HttpClientNotBuildable { source })?;
match &ctc.server_ca_path {
Some(path) => {
let mut ca_cert_vec: Vec<u8> = vec![];
File::open(path)
.map_err(|source| RedactStorerError::ServerCaCertFileNotReadable {
source,
})?
.read_to_end(&mut ca_cert_vec)
.map_err(|source| RedactStorerError::ServerCaCertFileNotReadable {
source,
})?;
let ca_cert =
reqwest::Certificate::from_pem(&ca_cert_vec).map_err(|source| {
RedactStorerError::HttpClientNotBuildable { source }
})?;
Ok::<_, RedactStorerError>(
reqwest::Client::builder()
.identity(pkcs12)
.add_root_certificate(ca_cert)
.tls_built_in_root_certs(false)
.use_rustls_tls()
.default_headers(headers)
.build()
.map_err(|source| RedactStorerError::HttpClientNotBuildable {
source,
})?,
)
}
None => Ok::<_, RedactStorerError>(
reqwest::Client::builder()
.identity(pkcs12)
.use_rustls_tls()
.default_headers(headers)
.build()
.map_err(|source| RedactStorerError::HttpClientNotBuildable {
source,
})?,
),
}
}
None => Ok(reqwest::Client::builder()
.use_rustls_tls()
.default_headers(headers)
.build()
.map_err(|source| RedactStorerError::HttpClientNotBuildable { source })?),
}
}
}
#[async_trait]
impl IndexedStorer for RedactStorer {
async fn get_indexed<T: StorableType>(
&self,
path: &str,
index: &Option<Document>,
) -> Result<Entry<T>, CryptoError> {
let mut req_url = format!("{}/{}?", &self.url, path);
if let Some(i) = index {
req_url.push_str(format!("index={}", i).as_ref());
}
let http_client = RedactStorer::get_http_client()?;
match http_client.get(&req_url).send().await {
Ok(r) => Ok(r
.error_for_status()
.map_err(|source| -> CryptoError {
if source.status() == Some(reqwest::StatusCode::NOT_FOUND) {
RedactStorerError::NotFound.into()
} else {
RedactStorerError::InternalError {
source: Box::new(source),
}
.into()
}
})?
.json::<Entry<T>>()
.await
.map_err(|source| -> CryptoError {
RedactStorerError::InternalError {
source: Box::new(source),
}
.into()
})?),
Err(source) => Err(RedactStorerError::InternalError {
source: Box::new(source),
}
.into()),
}
}
async fn list_indexed<T: StorableType>(
&self,
path: &str,
skip: u64,
page_size: i64,
index: &Option<Document>,
) -> Result<Vec<Entry<T>>, CryptoError> {
let mut req_url = format!(
"{}/{}?skip={}&page_size={}",
&self.url, path, skip, page_size
);
if let Some(i) = index {
req_url.push_str(format!("&index={}", i).as_ref());
}
let http_client = RedactStorer::get_http_client()?;
match http_client.get(&req_url).send().await {
Ok(r) => Ok(r
.error_for_status()
.map_err(|source| -> CryptoError {
if source.status() == Some(reqwest::StatusCode::NOT_FOUND) {
RedactStorerError::NotFound.into()
} else {
RedactStorerError::InternalError {
source: Box::new(source),
}
.into()
}
})?
.json::<Vec<Entry<T>>>()
.await
.map_err(|source| -> CryptoError {
RedactStorerError::InternalError {
source: Box::new(source),
}
.into()
})?),
Err(source) => Err(RedactStorerError::InternalError {
source: Box::new(source),
}
.into()),
}
}
}
#[async_trait]
impl Storer for RedactStorer {
async fn get<T: StorableType>(&self, path: &str) -> Result<Entry<T>, CryptoError> {
self.get_indexed::<T>(path, &T::get_index()).await
}
async fn create<T: StorableType>(&self, entry: Entry<T>) -> Result<Entry<T>, CryptoError> {
let value = serde_json::to_value(&entry).map_err(|e| RedactStorerError::InternalError {
source: Box::new(e),
})?;
let http_client = RedactStorer::get_http_client()?;
http_client
.post(&format!("{}/", self.url))
.json(&value)
.send()
.await
.and_then(|res| res.error_for_status().map(|_| entry))
.map_err(|e| {
if let Some(status) = e.status() {
if status == StatusCode::NOT_FOUND {
RedactStorerError::NotFound.into()
} else {
RedactStorerError::InternalError {
source: Box::new(e),
}
.into()
}
} else {
RedactStorerError::InternalError {
source: Box::new(e),
}
.into()
}
})
}
}