Skip to content

Commit

Permalink
Increase generics in format to internal adapters with `FileFormatMapA…
Browse files Browse the repository at this point in the history
…dapter`
  • Loading branch information
gibbz00 committed Dec 27, 2023
1 parent ec494d1 commit 1c0bf98
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 158 deletions.
93 changes: 85 additions & 8 deletions crates/rops/src/rops_file/format/core.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,104 @@
use std::fmt::Debug;

use indexmap::IndexMap;
use serde::{de::DeserializeOwned, Serialize};

use crate::*;

pub trait FileFormat: Sized {
type Map: Serialize + DeserializeOwned + PartialEq + Debug;
type Map: FileFormatMapAdapter;

type SerializeError: std::error::Error + Send + Sync + 'static;
type DeserializeError: std::error::Error + Send + Sync + 'static;

fn serialize_to_string<T: Serialize>(t: &T) -> Result<String, Self::SerializeError>;

fn deserialize_from_str<T: DeserializeOwned>(str: &str) -> Result<T, Self::DeserializeError>;
}

// TODO: pub(crate)
pub trait FileFormatMapAdapter: Sized + Serialize + DeserializeOwned + PartialEq + Debug
where
Self: IntoIterator<Item = (Self::Key, Self::Value)>,
{
type Key;
type Value;

/// Only strings are allowed to be keys (SOPS requirement).
fn validate_key(key: Self::Key) -> Result<String, FormatToInternalMapError>;

fn new_string_key(key_string: String) -> Self::Key;

fn with_capacity(capacity: usize) -> Self;

fn insert(&mut self, key: Self::Key, value: Self::Value);

fn decrypted_format_to_internal<F, S: RopsMapState>(self, recursive_value_fn: F) -> Result<RopsMap<S>, FormatToInternalMapError>
where
F: Fn(Self::Value) -> Result<RopsTree<S>, FormatToInternalMapError>,
{
let mut tree_map = IndexMap::default();

for (format_key, format_value) in self {
let key_string = Self::validate_key(format_key)?;
tree_map.insert(key_string, recursive_value_fn(format_value)?);
}

Ok(tree_map.into())
}

fn decrypted_format_to_internal_value(format_value: Self::Value) -> Result<RopsTree<DecryptedMap>, FormatToInternalMapError>;

fn decrypted_internal_to_format(rops_map: RopsMap<DecryptedMap>) -> Self {
let mut format_map = Self::with_capacity(rops_map.len());

for (key, value) in rops_map.0 {
format_map.insert(Self::new_string_key(key), Self::decrypted_internal_to_format_value(value));
}

format_map
}

fn decrypted_internal_to_format_value(rops_tree: RopsTree<DecryptedMap>) -> Self::Value;

fn encrypted_format_to_internal<F, C: Cipher>(
self,
resolved_partial_encryption: ResolvedPartialEncrpytion,
recursive_value_fn: F,
) -> Result<RopsMap<EncryptedMap<C>>, FormatToInternalMapError>
where
F: Fn(Self::Value, ResolvedPartialEncrpytion) -> Result<RopsTree<EncryptedMap<C>>, FormatToInternalMapError>,
{
let mut tree_map = IndexMap::default();

for (yaml_key, yaml_value) in self {
let key_string = Self::validate_key(yaml_key)?;
let mut resolved_partial_encryption = resolved_partial_encryption;

if let ResolvedPartialEncrpytion::No(partial_encryption_config) = resolved_partial_encryption {
resolved_partial_encryption = partial_encryption_config.resolve(&key_string);
}

tree_map.insert(key_string, recursive_value_fn(yaml_value, resolved_partial_encryption)?);
}

Ok(tree_map.into())
}

fn encrypted_fomat_to_internal_value<C: Cipher>(
format_value: Self::Value,
resolved_partial_encryption: ResolvedPartialEncrpytion,
) -> Result<RopsTree<EncryptedMap<C>>, FormatToInternalMapError>;

fn encrypted_to_internal<C: Cipher>(
format_map: RopsFileFormatMap<EncryptedMap<C>, Self>,
partial_encryption: Option<&PartialEncryptionConfig>,
) -> Result<RopsMap<EncryptedMap<C>>, FormatToInternalMapError>;
fn encrypted_internal_to_format_map<C: Cipher>(internal_map: RopsMap<EncryptedMap<C>>) -> Self {
let mut format_map = Self::with_capacity(internal_map.len());

fn encrypted_from_internal<C: Cipher>(rops_map: RopsMap<EncryptedMap<C>>) -> Self::Map;
for (key, tree) in internal_map.0 {
format_map.insert(Self::new_string_key(key), Self::encrypted_internal_to_format_value(tree));
}

fn decrypted_to_internal(format_map: RopsFileFormatMap<DecryptedMap, Self>) -> Result<RopsMap<DecryptedMap>, FormatToInternalMapError>;
format_map
}

fn decrypted_from_internal(rops_map: RopsMap<DecryptedMap>) -> Self::Map;
fn encrypted_internal_to_format_value<C: Cipher>(internal_tree: RopsTree<EncryptedMap<C>>) -> Self::Value;
}
6 changes: 4 additions & 2 deletions crates/rops/src/rops_file/format/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ impl<C: Cipher, F: FileFormat> RopsFileFormatMap<EncryptedMap<C>, F> {
self,
partial_encryption: Option<&PartialEncryptionConfig>,
) -> Result<RopsMap<EncryptedMap<C>>, FormatToInternalMapError> {
F::encrypted_to_internal(self, partial_encryption)
self.into_inner_map()
.encrypted_format_to_internal(partial_encryption.into(), F::Map::encrypted_fomat_to_internal_value)
}
}

impl<F: FileFormat> RopsFileFormatMap<DecryptedMap, F> {
pub fn to_internal(self) -> Result<RopsMap<DecryptedMap>, FormatToInternalMapError> {
F::decrypted_to_internal(self)
self.into_inner_map()
.decrypted_format_to_internal(F::Map::decrypted_format_to_internal_value)
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/rops/src/rops_file/format/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod core;
pub use core::FileFormat;
pub use core::{FileFormat, FileFormatMapAdapter};

mod map;
pub use map::{FormatToInternalMapError, RopsFileFormatMap};
Expand Down
220 changes: 87 additions & 133 deletions crates/rops/src/rops_file/format/yaml/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use indexmap::IndexMap;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::{Mapping as YamlMap, Value as YamlValue};

Expand All @@ -20,130 +19,109 @@ impl FileFormat for YamlFileFormat {
fn deserialize_from_str<T: DeserializeOwned>(str: &str) -> Result<T, Self::DeserializeError> {
serde_yaml::from_str(str)
}
}

fn encrypted_to_internal<C: Cipher>(
map: RopsFileFormatMap<EncryptedMap<C>, YamlFileFormat>,
optional_partial_encryption_config: Option<&PartialEncryptionConfig>,
) -> Result<RopsMap<EncryptedMap<C>>, FormatToInternalMapError> {
return recursive_map(map.into_inner_map(), optional_partial_encryption_config.into());

fn recursive_map<Ci: Cipher>(
map: YamlMap,
resolved_partial_encryption: ResolvedPartialEncrpytion,
) -> Result<RopsMap<EncryptedMap<Ci>>, FormatToInternalMapError> {
let mut tree_map = IndexMap::default();

for (yaml_key, yaml_value) in map {
let key_string = tree_traversal::validate_key(yaml_key)?;
let mut resolved_partial_encryption = resolved_partial_encryption;

if let ResolvedPartialEncrpytion::No(partial_encryption_config) = resolved_partial_encryption {
resolved_partial_encryption = partial_encryption_config.resolve(&key_string);
}

tree_map.insert(key_string, recursive_value_call(yaml_value, resolved_partial_encryption)?);
}

Ok(tree_map.into())
}
impl FileFormatMapAdapter for YamlMap {
type Key = YamlValue;
type Value = YamlValue;

fn recursive_value_call<Ci: Cipher>(
yaml_value: YamlValue,
resolved_partial_encryption: ResolvedPartialEncrpytion,
) -> Result<RopsTree<EncryptedMap<Ci>>, FormatToInternalMapError> {
Ok(match yaml_value {
YamlValue::Tagged(tagged) => recursive_value_call(tagged.value, resolved_partial_encryption)?,
YamlValue::Mapping(map) => RopsTree::Map(recursive_map(map, resolved_partial_encryption)?),
YamlValue::String(string) => match resolved_partial_encryption.escape_encryption() {
true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(RopsValue::String(string))),
false => RopsTree::Leaf(RopsMapEncryptedLeaf::Encrypted(string.parse()?)),
},
YamlValue::Sequence(sequence) => RopsTree::Sequence(
sequence
.into_iter()
.map(|value| recursive_value_call(value, resolved_partial_encryption))
.collect::<Result<Vec<_>, _>>()?,
),
YamlValue::Null => RopsTree::Null,
YamlValue::Bool(bool) => match resolved_partial_encryption.escape_encryption() {
true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(RopsValue::Boolean(bool))),
false => return Err(FormatToInternalMapError::PlaintextWhenEncrypted(bool.to_string())),
},
YamlValue::Number(number) => match resolved_partial_encryption.escape_encryption() {
true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(tree_traversal::resolve_number(number)?)),
false => return Err(FormatToInternalMapError::PlaintextWhenEncrypted(number.to_string())),
},
})
fn validate_key(key: Self::Key) -> Result<String, FormatToInternalMapError> {
match key {
YamlValue::String(string) => Ok(string),
other => Err(FormatToInternalMapError::NonStringKey(
serde_yaml::to_string(&other).expect("yaml value not serializable"),
)),
}
}

fn encrypted_from_internal<C: Cipher>(rops_map: RopsMap<EncryptedMap<C>>) -> Self::Map {
return recursive_map(rops_map);

fn recursive_map<Ci: Cipher>(rops_map: RopsMap<EncryptedMap<Ci>>) -> YamlMap {
let mut yaml_map = YamlMap::with_capacity(rops_map.len());
fn new_string_key(key_string: String) -> Self::Key {
YamlValue::String(key_string)
}

for (key, tree) in rops_map.0 {
yaml_map.insert(YamlValue::String(key), recursive_tree(tree));
}
fn with_capacity(capacity: usize) -> Self {
YamlMap::with_capacity(capacity)
}

yaml_map
}
fn insert(&mut self, key: Self::Key, value: Self::Value) {
self.insert(key, value);
}

fn recursive_tree<Ci: Cipher>(internal_tree: RopsTree<EncryptedMap<Ci>>) -> YamlValue {
match internal_tree {
RopsTree::Sequence(sequence) => YamlValue::Sequence(sequence.into_iter().map(recursive_tree).collect()),
RopsTree::Map(map) => YamlValue::Mapping(recursive_map(map)),
RopsTree::Null => YamlValue::Null,
RopsTree::Leaf(maybe_encrypted_value) => match maybe_encrypted_value {
RopsMapEncryptedLeaf::Encrypted(encrypted_value) => YamlValue::String(encrypted_value.to_string()),
RopsMapEncryptedLeaf::Escaped(escaped_value) => tree_traversal::internal_to_yaml_value(escaped_value),
},
}
}
fn decrypted_format_to_internal_value(yaml_value: Self::Value) -> Result<RopsTree<DecryptedMap>, FormatToInternalMapError> {
Ok(match yaml_value {
// SOPS simply throws away tags, so do we for now.
// It can, however, deserialize manually added tags to encrypted documents,
// so we could in theory keep the tags somewhere without breaking SOPS compatability.
YamlValue::Tagged(tagged) => Self::decrypted_format_to_internal_value(tagged.value)?,
YamlValue::Mapping(map) => RopsTree::Map(YamlMap::decrypted_format_to_internal(
map,
Self::decrypted_format_to_internal_value,
)?),
YamlValue::Bool(boolean) => RopsTree::Leaf(RopsValue::Boolean(boolean)),
YamlValue::String(string) => RopsTree::Leaf(RopsValue::String(string)),
YamlValue::Number(number) => RopsTree::Leaf(tree_traversal::resolve_number(number)?),
YamlValue::Sequence(sequence) => RopsTree::Sequence(
sequence
.into_iter()
.map(Self::decrypted_format_to_internal_value)
.collect::<Result<Vec<_>, _>>()?,
),
YamlValue::Null => RopsTree::Null,
})
}

fn decrypted_to_internal(format_map: RopsFileFormatMap<DecryptedMap, Self>) -> Result<RopsMap<DecryptedMap>, FormatToInternalMapError> {
return tree_traversal::recursive_map_call(format_map.into_inner_map(), recursive_value_call);

fn recursive_value_call(yaml_value: YamlValue) -> Result<RopsTree<DecryptedMap>, FormatToInternalMapError> {
Ok(match yaml_value {
// SOPS simply throws away tags, so do we for now.
// It can, however, deserialize manually added tags to encrypted documents,
// so we could in theory keep the tags somewhere without breaking SOPS compatability.
YamlValue::Tagged(tagged) => recursive_value_call(tagged.value)?,
YamlValue::Mapping(map) => RopsTree::Map(tree_traversal::recursive_map_call(map, recursive_value_call)?),
YamlValue::Bool(boolean) => RopsTree::Leaf(RopsValue::Boolean(boolean)),
YamlValue::String(string) => RopsTree::Leaf(RopsValue::String(string)),
YamlValue::Number(number) => RopsTree::Leaf(tree_traversal::resolve_number(number)?),
YamlValue::Sequence(sequence) => {
RopsTree::Sequence(sequence.into_iter().map(recursive_value_call).collect::<Result<Vec<_>, _>>()?)
}
YamlValue::Null => RopsTree::Null,
})
fn decrypted_internal_to_format_value(rops_tree: RopsTree<DecryptedMap>) -> Self::Value {
match rops_tree {
RopsTree::Sequence(sequence) => {
YamlValue::Sequence(sequence.into_iter().map(Self::decrypted_internal_to_format_value).collect())
}
RopsTree::Map(map) => YamlValue::Mapping(Self::decrypted_internal_to_format(map)),
RopsTree::Null => YamlValue::Null,
RopsTree::Leaf(decrypted_value) => tree_traversal::internal_to_yaml_value(decrypted_value),
}
}

fn decrypted_from_internal(rops_map: RopsMap<DecryptedMap>) -> Self::Map {
return recursive_map(rops_map);

fn recursive_map(rops_map: RopsMap<DecryptedMap>) -> YamlMap {
let mut yaml_map = YamlMap::with_capacity(rops_map.len());

for (key, value) in rops_map.0 {
yaml_map.insert(YamlValue::String(key), recursive_tree(value));
fn encrypted_fomat_to_internal_value<C: Cipher>(
yaml_value: Self::Value,
resolved_partial_encryption: ResolvedPartialEncrpytion,
) -> Result<RopsTree<EncryptedMap<C>>, FormatToInternalMapError> {
Ok(match yaml_value {
YamlValue::Tagged(tagged) => Self::encrypted_fomat_to_internal_value(tagged.value, resolved_partial_encryption)?,
YamlValue::Mapping(map) => {
RopsTree::Map(map.encrypted_format_to_internal(resolved_partial_encryption, Self::encrypted_fomat_to_internal_value)?)
}
YamlValue::String(string) => match resolved_partial_encryption.escape_encryption() {
true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(RopsValue::String(string))),
false => RopsTree::Leaf(RopsMapEncryptedLeaf::Encrypted(string.parse()?)),
},
YamlValue::Sequence(sequence) => RopsTree::Sequence(
sequence
.into_iter()
.map(|value| Self::encrypted_fomat_to_internal_value(value, resolved_partial_encryption))
.collect::<Result<Vec<_>, _>>()?,
),
YamlValue::Null => RopsTree::Null,
YamlValue::Bool(bool) => match resolved_partial_encryption.escape_encryption() {
true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(RopsValue::Boolean(bool))),
false => return Err(FormatToInternalMapError::PlaintextWhenEncrypted(bool.to_string())),
},
YamlValue::Number(number) => match resolved_partial_encryption.escape_encryption() {
true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(tree_traversal::resolve_number(number)?)),
false => return Err(FormatToInternalMapError::PlaintextWhenEncrypted(number.to_string())),
},
})
}

yaml_map
}

fn recursive_tree(rops_tree: RopsTree<DecryptedMap>) -> YamlValue {
match rops_tree {
RopsTree::Sequence(sequence) => YamlValue::Sequence(sequence.into_iter().map(recursive_tree).collect()),
RopsTree::Map(map) => YamlValue::Mapping(recursive_map(map)),
RopsTree::Null => YamlValue::Null,
RopsTree::Leaf(decrypted_value) => tree_traversal::internal_to_yaml_value(decrypted_value),
fn encrypted_internal_to_format_value<C: Cipher>(internal_tree: RopsTree<EncryptedMap<C>>) -> Self::Value {
match internal_tree {
RopsTree::Sequence(sequence) => {
YamlValue::Sequence(sequence.into_iter().map(Self::encrypted_internal_to_format_value).collect())
}
RopsTree::Map(map) => YamlValue::Mapping(Self::encrypted_internal_to_format_map(map)),
RopsTree::Null => YamlValue::Null,
RopsTree::Leaf(maybe_encrypted_value) => match maybe_encrypted_value {
RopsMapEncryptedLeaf::Encrypted(encrypted_value) => YamlValue::String(encrypted_value.to_string()),
RopsMapEncryptedLeaf::Escaped(escaped_value) => tree_traversal::internal_to_yaml_value(escaped_value),
},
}
}
}
Expand All @@ -170,30 +148,6 @@ mod tree_traversal {
RopsValue::Float(rops_float) => YamlValue::Number(f64::from(rops_float).into()),
}
}

pub fn recursive_map_call<F, S: RopsMapState>(yaml_map: YamlMap, recursive_value_fn: F) -> Result<RopsMap<S>, FormatToInternalMapError>
where
F: Fn(YamlValue) -> Result<RopsTree<S>, FormatToInternalMapError>,
{
let mut tree_map = IndexMap::default();

for (yaml_key, value_yaml) in yaml_map {
let key_string = validate_key(yaml_key)?;

tree_map.insert(key_string, recursive_value_fn(value_yaml)?);
}

Ok(tree_map.into())
}

pub fn validate_key(yaml_value: YamlValue) -> Result<String, FormatToInternalMapError> {
match yaml_value {
YamlValue::String(string) => Ok(string),
other => Err(FormatToInternalMapError::NonStringKey(
serde_yaml::to_string(&other).expect("yaml value not serializable"),
)),
}
}
}

#[cfg(feature = "test-utils")]
Expand Down
Loading

0 comments on commit 1c0bf98

Please sign in to comment.