redact-client 1.5.9

Receives request for private data and decrypts it to display securely in browser
pub mod get;
pub mod post;

use bytes::buf::BufMut;
use futures::TryStreamExt;
use redact_crypto::{
    BinaryData, BinaryType, CryptoError, Data, Storer, SymmetricKey, ToEntry,
    ToSymmetricByteAlgorithm,
};
use std::{convert::TryFrom, sync::Arc};
use warp::{multipart::FormData, Filter, Rejection, Reply};

use crate::{
    relayer::Relayer,
    render::Renderer,
    routes::{
        error::RelayRejection, validated_query_params, BadRequestRejection, CryptoErrorRejection,
    },
    token::TokenGenerator,
};

pub fn get<R: Renderer + Clone + Send + 'static, H: Storer, T: TokenGenerator>(
    storer: Arc<H>,
    render_engine: R,
    token_generator: T,
) -> impl Filter<Extract = (Box<dyn Reply>, String, Option<String>, Option<String>), Error = Rejection>
       + Clone {
    warp::get()
        .and(warp::path!(String / String))
        .and(warp::any().map(move || token_generator.clone().generate_token().unwrap()))
        .and(validated_query_params::<get::QueryParams>())
        .and(warp::any().map(move || storer.clone()))
        .and(warp::any().map(move || render_engine.clone()))
        .and_then(
            move |path: String,
                  old_token: String,
                  new_token: String,
                  query: get::QueryParams,
                  storer: Arc<H>,
                  render_engine: R| async move {
                let data_entry = match storer.get::<Data>(&path).await {
                    Ok(e) => Ok(Some(e)),
                    Err(e) => match e {
                        CryptoError::NotFound { .. } => Ok(None),
                        _ => Err(e),
                    },
                }
                .map_err(CryptoErrorRejection)?;

                let data = match data_entry {
                    Some(data_entry) => data_entry
                        .take_resolve()
                        .await
                        .map_err(CryptoErrorRejection)?,
                    None => {
                        if let Some(data_type) = query.data_type.clone() {
                            match data_type.to_ascii_lowercase().as_ref() {
                                "bool" => Data::Bool(false),
                                "u64" => Data::U64(0),
                                "i64" => Data::I64(0),
                                "f64" => Data::F64(0.0),
                                "media" => Data::Binary(None),
                                _ => Data::String("".to_owned()),
                            }
                        } else {
                            Data::String("".to_owned())
                        }
                    }
                };

                let new_path: Option<String> = match query.edit {
                    Some(true) => Some(format!("/secure/data/{}/{}", &path, &new_token)),
                    _ => None,
                };

                Ok::<_, Rejection>((
                    Box::new(get::reply(data, &path, &new_token, query, &render_engine)?)
                        as Box<dyn Reply>,
                    format!("/secure/data/{}/{}", &path, &old_token),
                    new_path,
                    Some(new_token),
                ))
            },
        )
        .untuple_one()
}

pub fn post<R: Renderer + Clone + Send + 'static, T: TokenGenerator, H: Storer, Q: Relayer>(
    render_engine: R,
    token_generator: T,
    storer: Arc<H>,
    relayer: Q,
) -> impl Filter<Extract = (Box<dyn Reply>, String, Option<String>, Option<String>), Error = Rejection>
       + Clone {
    warp::post()
        .and(warp::path!(String / String))
        .and(warp::query::<post::QueryParams>())
        .and(
            warp::filters::body::form::<post::BodyParams>()
                .and_then(move |body: post::BodyParams| async {
                    let path = body.path.clone();
                    Ok::<_, Rejection>((Data::try_from(body)?, path))
                })
                .or(warp::filters::multipart::form()
                    .max_length(1024 * 1024 * 16) // 16 MB
                    .and_then(|form: FormData| async {
                        let binary_type: Option<BinaryType> = None;
                        let binary_data: Option<String> = None;
                        let path: Option<String> = None;

                        let (binary_type, binary_data, path): (
                            Option<BinaryType>,
                            Option<String>,
                            Option<String>,
                        ) = form
                            .try_fold(
                                (binary_type, binary_data, path),
                                |(mut bt, mut bd, mut p), x| async move {
                                    let field_name = x.name().to_owned();
                                    let content_type = x.content_type();

                                    if field_name == "path" {
                                        let data = x
                                            .stream()
                                            .try_fold(Vec::new(), |mut vec, data| {
                                                vec.put(data);
                                                async move { Ok(vec) }
                                            })
                                            .await?;

                                        p = match std::str::from_utf8(&data) {
                                            Ok(d) => Some(d.to_string()),
                                            Err(_) => None,
                                        };
                                    } else if field_name == "value" {
                                        bt = match BinaryType::try_from(
                                            content_type.unwrap_or_default(),
                                        ) {
                                            Ok(binary_type) => Some(binary_type),
                                            Err(_) => None,
                                        };
                                        let data = x
                                            .stream()
                                            .try_fold(Vec::new(), |mut vec, data| {
                                                vec.put(data);
                                                async move { Ok(vec) }
                                            })
                                            .await?;
                                        bd = Some(base64::encode(data));
                                    }
                                    Ok((bt, bd, p))
                                },
                            )
                            .await
                            .map_err(|_| warp::reject::custom(BadRequestRejection))?;

                        let bd = BinaryData {
                            binary: binary_data
                                .ok_or_else(|| warp::reject::custom(BadRequestRejection))?,
                            binary_type: binary_type
                                .ok_or_else(|| warp::reject::custom(BadRequestRejection))?,
                        };
                        Ok::<_, Rejection>((
                            Data::Binary(Some(bd)),
                            path.ok_or_else(|| warp::reject::custom(BadRequestRejection))?,
                        ))
                    }))
                .unify(),
        )
        .and(warp::any().map(move || token_generator.clone().generate_token().unwrap()))
        .and(warp::any().map(move || render_engine.clone()))
        .and(warp::any().map(move || storer.clone()))
        .and(warp::any().map(move || relayer.clone()))
        .and_then(
            move |_query_data_path: String,
                  old_token: String,
                  query: post::QueryParams,
                  (data, path): (Data, String),
                  new_token: String,
                  render_engine: R,
                  storer: Arc<H>,
                  relayer: Q| async move {
                let key_entry = storer
                    .get::<SymmetricKey>(".keys.encryption.symmetric.default.")
                    .await
                    .map_err(CryptoErrorRejection)?;
                let (key, key_entry_path, _) = key_entry
                    .take_resolve_all()
                    .await
                    .map_err(CryptoErrorRejection)?;
                let algo_storer = (*storer).clone();
                let key_algo = key
                    .to_byte_algorithm(None, |key| async move {
                        key.to_ref_entry(key_entry_path, algo_storer)
                    })
                    .await
                    .map_err(CryptoErrorRejection)?;
                let data_clone = data.clone();
                let entry = data_clone
                    .to_sealed_entry(path.clone(), key_algo)
                    .await
                    .map_err(CryptoErrorRejection)?;
                storer.create(entry).await.map_err(CryptoErrorRejection)?;

                if let Some(relay_url) = query.relay_url.clone() {
                    relayer
                        .relay(path.clone(), relay_url)
                        .await
                        .map_err(|_| warp::reject::custom(RelayRejection))?;
                }

                let reply = post::reply(data, &path, &new_token, query, &render_engine)?;
                Ok::<_, Rejection>((
                    Box::new(reply) as Box<dyn Reply>,
                    format!("/secure/data/{}/{}", &path, &old_token),
                    Some(format!("/secure/data/{}/{}", &path, &new_token)),
                    Some(new_token),
                ))
            },
        )
        .untuple_one()
}