cloudiful-redactor 0.2.9

Structured text redaction with reversible sessions for secrets, domains, URLs, and related sensitive values.
Documentation
use anyhow::{Context, Result};

use crate::{
    InputKind, RedactionArtifact, RedactionSession, Redactor, RestoreResult,
    decrypt_session_from_str, encrypt_session_to_string, ensure_restore_valid,
    inspect_encrypted_session,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedRedactionArtifact {
    pub artifact: RedactionArtifact,
    pub encrypted_session: String,
    pub session_summary: crate::SessionSummary,
}

pub fn redact_text_artifact(
    redactor: &Redactor,
    text: &str,
    input_kind: InputKind,
) -> Result<RedactionArtifact> {
    redactor
        .redact_artifact_with_input_kind(text, input_kind)
        .map_err(anyhow::Error::new)
}

pub fn redact_text_artifact_with_source(
    redactor: &Redactor,
    text: &str,
    input_kind: InputKind,
    source_path: &str,
) -> Result<RedactionArtifact> {
    redactor
        .redact_artifact_with_input_kind_and_source(text, input_kind, Some(source_path))
        .map_err(anyhow::Error::new)
}

pub fn redact_text_with_encrypted_session(
    redactor: &Redactor,
    text: &str,
    input_kind: InputKind,
    passphrase: &str,
) -> Result<EncryptedRedactionArtifact> {
    let artifact =
        redact_text_artifact(redactor, text, input_kind).context("failed to redact text input")?;
    let encrypted_session = encrypt_session_to_string(&artifact.session, passphrase)
        .context("failed to encrypt redaction session")?;
    let session_summary = inspect_encrypted_session(&encrypted_session)
        .context("failed to inspect encrypted session")?;

    Ok(EncryptedRedactionArtifact {
        artifact,
        encrypted_session,
        session_summary,
    })
}

pub fn redact_text_with_encrypted_session_and_source(
    redactor: &Redactor,
    text: &str,
    input_kind: InputKind,
    source_path: &str,
    passphrase: &str,
) -> Result<EncryptedRedactionArtifact> {
    let artifact = redact_text_artifact_with_source(redactor, text, input_kind, source_path)
        .context("failed to redact text input")?;
    let encrypted_session = encrypt_session_to_string(&artifact.session, passphrase)
        .context("failed to encrypt redaction session")?;
    let session_summary = inspect_encrypted_session(&encrypted_session)
        .context("failed to inspect encrypted session")?;

    Ok(EncryptedRedactionArtifact {
        artifact,
        encrypted_session,
        session_summary,
    })
}

pub fn decrypt_redaction_session(
    encrypted_session: &str,
    passphrase: &str,
) -> Result<RedactionSession> {
    decrypt_session_from_str(encrypted_session, passphrase)
        .context("failed to decrypt provided session")
}

pub fn restore_text_from_encrypted_session(
    redactor: &Redactor,
    text: &str,
    encrypted_session: &str,
    passphrase: &str,
) -> Result<RestoreResult> {
    let session = decrypt_redaction_session(encrypted_session, passphrase)?;
    let restored = redactor.restore_text(text, &session);
    ensure_restore_valid(&restored)?;
    Ok(restored)
}

#[cfg(test)]
mod tests {
    use super::{
        redact_text_artifact, redact_text_with_encrypted_session,
        restore_text_from_encrypted_session,
    };
    use crate::{InputKind, RedactionPolicy, RedactorBuilder};
    use crate::types::FindingKind;

    fn full_redactor() -> crate::Redactor {
        RedactorBuilder::new()
            .with_redaction_policy(
                RedactionPolicy::default()
                    .with_kind(FindingKind::Domain, true)
                    .with_kind(FindingKind::Secret, true)
                    .with_kind(FindingKind::Url, true),
            )
            .build()
    }

    #[test]
    fn encrypted_redaction_matches_plain_artifact_output() {
        let redactor = full_redactor();
        let text = "host=service.example.com secret=EJ2QEVC6AKELW0k2kkVY4NgGKONC";
        let plain = redact_text_artifact(&redactor, text, InputKind::Text).expect("plain");
        let encrypted =
            redact_text_with_encrypted_session(&redactor, text, InputKind::Text, "pass")
                .expect("encrypted");

        assert_eq!(
            encrypted.artifact.result.redacted_text,
            plain.result.redacted_text
        );
        assert_eq!(
            encrypted.artifact.session.redacted_text,
            plain.session.redacted_text
        );
    }

    #[test]
    fn encrypted_session_restore_round_trips() {
        let redactor = full_redactor();
        let text = "host=service.example.com";
        let encrypted =
            redact_text_with_encrypted_session(&redactor, text, InputKind::Text, "pass")
                .expect("encrypted");

        let restored = restore_text_from_encrypted_session(
            &redactor,
            &encrypted.artifact.result.redacted_text,
            &encrypted.encrypted_session,
            "pass",
        )
        .expect("restore");

        assert_eq!(restored.restored_text, text);
    }
}