libvault 0.2.2

the libvault is modified from RustyVault
Documentation
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};

use regex::Regex;
use serde_json::{Map, Value};

use super::{
    Backend, FieldType, Operation,
    path::{Path, PathBuilder},
    request::Request,
    response::Response,
    secret::{Secret, SecretBuilder},
};
use crate::{context::Context, errors::RvError};

type BackendOperationHandler = dyn for<'a> Fn(
        &'a dyn Backend,
        &'a mut Request,
    ) -> Pin<Box<dyn Future<Output = Result<Option<Response>, RvError>> + Send + 'a>>
    + Send
    + Sync;

pub const CTX_KEY_BACKEND_PATH: &str = "backend.path";

#[derive(Clone)]
pub struct LogicalBackend {
    pub paths: Vec<Arc<Path>>,
    pub paths_re: Vec<Regex>,
    pub root_paths: Arc<Vec<String>>,
    pub unauth_paths: Arc<Vec<String>>,
    pub help: String,
    pub secrets: Vec<Arc<Secret>>,
    pub auth_renew_handler: Option<Arc<BackendOperationHandler>>,
    pub ctx: Arc<Context>,
}

#[async_trait::async_trait]
impl Backend for LogicalBackend {
    fn init(&mut self) -> Result<(), RvError> {
        if self.paths.len() == self.paths_re.len() {
            return Ok(());
        }

        for path in &self.paths {
            let mut pattern = path.pattern.clone();
            if !path.pattern.starts_with('^') {
                pattern = format!("^{}", &pattern);
            }

            if !path.pattern.ends_with('$') {
                pattern = format!("{}$", &pattern);
            }

            let re = Regex::new(&pattern)?;
            self.paths_re.push(re);
        }

        Ok(())
    }

    fn setup(&self, _key: &str) -> Result<(), RvError> {
        Ok(())
    }

    fn cleanup(&self) -> Result<(), RvError> {
        Ok(())
    }

    fn get_unauth_paths(&self) -> Option<Arc<Vec<String>>> {
        Some(self.unauth_paths.clone())
    }

    fn get_root_paths(&self) -> Option<Arc<Vec<String>>> {
        Some(self.root_paths.clone())
    }

    fn get_ctx(&self) -> Option<Arc<Context>> {
        Some(self.ctx.clone())
    }

    async fn handle_request(&self, req: &mut Request) -> Result<Option<Response>, RvError> {
        if req.storage.is_none() {
            return Err(RvError::ErrRequestNotReady);
        }

        match req.operation {
            Operation::Renew | Operation::Revoke => {
                return self.handle_revoke_renew(req).await;
            }
            _ => {}
        }

        if req.path.is_empty() && req.operation == Operation::Help {
            return self.handle_root_help(req).await;
        }

        if let Some((path, captures)) = self.match_path(&req.path) {
            if !captures.is_empty() {
                let mut data = Map::new();
                captures.iter().for_each(|(key, value)| {
                    data.insert(key.to_string(), Value::String(value.to_string()));
                });
                req.data = Some(data);
            }

            req.match_path = Some(path.clone());
            for operation in &path.operations {
                if operation.op == req.operation {
                    self.ctx.set(CTX_KEY_BACKEND_PATH, path.clone());
                    let ret = operation.handle_request(self, req).await;
                    self.clear_secret_field(req);
                    return ret;
                }
            }

            return Err(RvError::ErrLogicalOperationUnsupported);
        }

        Err(RvError::ErrLogicalPathUnsupported)
    }

    fn secret(&self, key: &str) -> Option<&Arc<Secret>> {
        self.secrets.iter().find(|s| s.secret_type == key)
    }
}

impl LogicalBackend {
    pub fn new() -> Self {
        Self {
            paths: Vec::new(),
            paths_re: Vec::new(),
            root_paths: Arc::new(Vec::new()),
            unauth_paths: Arc::new(Vec::new()),
            help: String::new(),
            secrets: Vec::new(),
            auth_renew_handler: None,
            ctx: Arc::new(Context::new()),
        }
    }

    pub fn builder() -> LogicalBackendBuilder {
        LogicalBackendBuilder::new()
    }

    pub async fn handle_auth_renew(&self, req: &mut Request) -> Result<Option<Response>, RvError> {
        let Some(auth_renew_handler) = self.auth_renew_handler.as_ref() else {
            log::error!("this auth type doesn't support renew");
            return Err(RvError::ErrLogicalOperationUnsupported);
        };

        auth_renew_handler(self, req).await
    }

    pub async fn handle_revoke_renew(
        &self,
        req: &mut Request,
    ) -> Result<Option<Response>, RvError> {
        if req.operation == Operation::Renew && req.auth.is_some() {
            return self.handle_auth_renew(req).await;
        }

        if req.secret.is_none() {
            log::error!("request has no secret");
            return Ok(None);
        }

        if let Some(raw_secret_type) = req
            .secret
            .as_ref()
            .unwrap()
            .internal_data
            .get("secret_type")
            && let Some(secret_type) = raw_secret_type.as_str()
            && let Some(secret) = self.secret(secret_type)
        {
            match req.operation {
                Operation::Renew => {
                    return secret.renew(self, req).await;
                }
                Operation::Revoke => {
                    return secret.revoke(self, req).await;
                }
                _ => {
                    log::error!("invalid operation for revoke/renew: {}", req.operation);
                    return Ok(None);
                }
            }
        }

        log::error!("secret is unsupported by this backend");
        Ok(None)
    }

    pub async fn handle_root_help(&self, _req: &mut Request) -> Result<Option<Response>, RvError> {
        Ok(None)
    }

    pub fn match_path(&self, path: &str) -> Option<(Arc<Path>, HashMap<String, String>)> {
        for (i, re) in self.paths_re.iter().enumerate() {
            if let Some(matches) = re.captures(path) {
                let mut captures = HashMap::new();
                let path = self.paths[i].clone();
                for (i, name) in re.capture_names().enumerate() {
                    if let Some(name) = name {
                        captures.insert(name.to_string(), matches[i].to_string());
                    }
                }

                return Some((path, captures));
            }
        }

        None
    }

    fn clear_secret_field(&self, req: &mut Request) {
        for path in &self.paths {
            for (key, field) in &path.fields {
                if field.field_type == FieldType::SecretStr {
                    req.clear_data(key);
                }
            }
        }
    }
}

pub trait IntoPathArc {
    fn into_path_arc(self) -> Arc<Path>;
}

impl IntoPathArc for Arc<Path> {
    fn into_path_arc(self) -> Arc<Path> {
        self
    }
}

impl IntoPathArc for Path {
    fn into_path_arc(self) -> Arc<Path> {
        Arc::new(self)
    }
}

impl IntoPathArc for PathBuilder {
    fn into_path_arc(self) -> Arc<Path> {
        Arc::new(self.build())
    }
}

pub trait IntoSecretArc {
    fn into_secret_arc(self) -> Arc<Secret>;
}

impl IntoSecretArc for Arc<Secret> {
    fn into_secret_arc(self) -> Arc<Secret> {
        self
    }
}

impl IntoSecretArc for Secret {
    fn into_secret_arc(self) -> Arc<Secret> {
        Arc::new(self)
    }
}

impl IntoSecretArc for SecretBuilder {
    fn into_secret_arc(self) -> Arc<Secret> {
        self.build_arc()
    }
}

#[derive(Clone)]
pub struct LogicalBackendBuilder {
    backend: LogicalBackend,
}

impl Default for LogicalBackendBuilder {
    fn default() -> Self {
        Self {
            backend: LogicalBackend::new(),
        }
    }
}

impl LogicalBackendBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn context(mut self, ctx: Arc<Context>) -> Self {
        self.backend.ctx = ctx;
        self
    }

    pub fn help(mut self, help: impl Into<String>) -> Self {
        self.backend.help = help.into();
        self
    }

    pub fn path<P>(mut self, path: P) -> Self
    where
        P: IntoPathArc,
    {
        self.backend.paths.push(path.into_path_arc());
        self
    }

    pub fn paths<I, P>(mut self, paths: I) -> Self
    where
        I: IntoIterator<Item = P>,
        P: IntoPathArc,
    {
        self.backend
            .paths
            .extend(paths.into_iter().map(|p| p.into_path_arc()));
        self
    }

    pub fn secret<S>(mut self, secret: S) -> Self
    where
        S: IntoSecretArc,
    {
        self.backend.secrets.push(secret.into_secret_arc());
        self
    }

    pub fn secrets<I, S>(mut self, secrets: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: IntoSecretArc,
    {
        self.backend
            .secrets
            .extend(secrets.into_iter().map(|s| s.into_secret_arc()));
        self
    }

    pub fn unauth_paths<I, S>(mut self, paths: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.backend.unauth_paths = Arc::new(paths.into_iter().map(Into::into).collect());
        self
    }

    pub fn add_unauth_path(mut self, path: impl Into<String>) -> Self {
        Arc::make_mut(&mut self.backend.unauth_paths).push(path.into());
        self
    }

    pub fn root_paths<I, S>(mut self, paths: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.backend.root_paths = Arc::new(paths.into_iter().map(Into::into).collect());
        self
    }

    pub fn add_root_path(mut self, path: impl Into<String>) -> Self {
        Arc::make_mut(&mut self.backend.root_paths).push(path.into());
        self
    }

    pub fn auth_renew_handler<H>(mut self, handler: H) -> Self
    where
        H: for<'a> Fn(
                &'a dyn Backend,
                &'a mut Request,
            ) -> Pin<
                Box<dyn Future<Output = Result<Option<Response>, RvError>> + Send + 'a>,
            > + Send
            + Sync
            + 'static,
    {
        self.backend.auth_renew_handler = Some(Arc::new(handler));
        self
    }

    pub fn build(self) -> LogicalBackend {
        self.backend
    }

    pub fn build_arc(self) -> Arc<LogicalBackend> {
        Arc::new(self.backend)
    }
}