diff --git a/crates/rops/src/rops_file/format/core.rs b/crates/rops/src/rops_file/format/core.rs index f88e3cb..90d0edc 100644 --- a/crates/rops/src/rops_file/format/core.rs +++ b/crates/rops/src/rops_file/format/core.rs @@ -1,11 +1,12 @@ 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; @@ -13,15 +14,91 @@ pub trait FileFormat: Sized { fn serialize_to_string(t: &T) -> Result; fn deserialize_from_str(str: &str) -> Result; +} + +// TODO: pub(crate) +pub trait FileFormatMapAdapter: Sized + Serialize + DeserializeOwned + PartialEq + Debug +where + Self: IntoIterator, +{ + type Key; + type Value; + + /// Only strings are allowed to be keys (SOPS requirement). + fn validate_key(key: Self::Key) -> Result; + + 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(self, recursive_value_fn: F) -> Result, FormatToInternalMapError> + where + F: Fn(Self::Value) -> Result, 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, FormatToInternalMapError>; + + fn decrypted_internal_to_format(rops_map: RopsMap) -> 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) -> Self::Value; + + fn encrypted_format_to_internal( + self, + resolved_partial_encryption: ResolvedPartialEncrpytion, + recursive_value_fn: F, + ) -> Result>, FormatToInternalMapError> + where + F: Fn(Self::Value, ResolvedPartialEncrpytion) -> Result>, 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( + format_value: Self::Value, + resolved_partial_encryption: ResolvedPartialEncrpytion, + ) -> Result>, FormatToInternalMapError>; - fn encrypted_to_internal( - format_map: RopsFileFormatMap, Self>, - partial_encryption: Option<&PartialEncryptionConfig>, - ) -> Result>, FormatToInternalMapError>; + fn encrypted_internal_to_format_map(internal_map: RopsMap>) -> Self { + let mut format_map = Self::with_capacity(internal_map.len()); - fn encrypted_from_internal(rops_map: RopsMap>) -> 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) -> Result, FormatToInternalMapError>; + format_map + } - fn decrypted_from_internal(rops_map: RopsMap) -> Self::Map; + fn encrypted_internal_to_format_value(internal_tree: RopsTree>) -> Self::Value; } diff --git a/crates/rops/src/rops_file/format/map.rs b/crates/rops/src/rops_file/format/map.rs index 0eafd9d..79540ae 100644 --- a/crates/rops/src/rops_file/format/map.rs +++ b/crates/rops/src/rops_file/format/map.rs @@ -46,13 +46,15 @@ impl RopsFileFormatMap, F> { self, partial_encryption: Option<&PartialEncryptionConfig>, ) -> Result>, 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 RopsFileFormatMap { pub fn to_internal(self) -> Result, FormatToInternalMapError> { - F::decrypted_to_internal(self) + self.into_inner_map() + .decrypted_format_to_internal(F::Map::decrypted_format_to_internal_value) } } diff --git a/crates/rops/src/rops_file/format/mod.rs b/crates/rops/src/rops_file/format/mod.rs index 3762667..ae5dac4 100644 --- a/crates/rops/src/rops_file/format/mod.rs +++ b/crates/rops/src/rops_file/format/mod.rs @@ -1,5 +1,5 @@ mod core; -pub use core::FileFormat; +pub use core::{FileFormat, FileFormatMapAdapter}; mod map; pub use map::{FormatToInternalMapError, RopsFileFormatMap}; diff --git a/crates/rops/src/rops_file/format/yaml/mod.rs b/crates/rops/src/rops_file/format/yaml/mod.rs index 6134204..5313fce 100644 --- a/crates/rops/src/rops_file/format/yaml/mod.rs +++ b/crates/rops/src/rops_file/format/yaml/mod.rs @@ -1,4 +1,3 @@ -use indexmap::IndexMap; use serde::{de::DeserializeOwned, Serialize}; use serde_yaml::{Mapping as YamlMap, Value as YamlValue}; @@ -20,130 +19,109 @@ impl FileFormat for YamlFileFormat { fn deserialize_from_str(str: &str) -> Result { serde_yaml::from_str(str) } +} - fn encrypted_to_internal( - map: RopsFileFormatMap, YamlFileFormat>, - optional_partial_encryption_config: Option<&PartialEncryptionConfig>, - ) -> Result>, FormatToInternalMapError> { - return recursive_map(map.into_inner_map(), optional_partial_encryption_config.into()); - - fn recursive_map( - map: YamlMap, - resolved_partial_encryption: ResolvedPartialEncrpytion, - ) -> Result>, 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( - yaml_value: YamlValue, - resolved_partial_encryption: ResolvedPartialEncrpytion, - ) -> Result>, 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::, _>>()?, - ), - 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 { + 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(rops_map: RopsMap>) -> Self::Map { - return recursive_map(rops_map); - - fn recursive_map(rops_map: RopsMap>) -> 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(internal_tree: RopsTree>) -> 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, 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::, _>>()?, + ), + YamlValue::Null => RopsTree::Null, + }) } - fn decrypted_to_internal(format_map: RopsFileFormatMap) -> Result, FormatToInternalMapError> { - return tree_traversal::recursive_map_call(format_map.into_inner_map(), recursive_value_call); - - fn recursive_value_call(yaml_value: YamlValue) -> Result, 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::, _>>()?) - } - YamlValue::Null => RopsTree::Null, - }) + fn decrypted_internal_to_format_value(rops_tree: RopsTree) -> 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) -> Self::Map { - return recursive_map(rops_map); - - fn recursive_map(rops_map: RopsMap) -> 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( + yaml_value: Self::Value, + resolved_partial_encryption: ResolvedPartialEncrpytion, + ) -> Result>, 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::, _>>()?, + ), + 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) -> 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(internal_tree: RopsTree>) -> 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), + }, } } } @@ -170,30 +148,6 @@ mod tree_traversal { RopsValue::Float(rops_float) => YamlValue::Number(f64::from(rops_float).into()), } } - - pub fn recursive_map_call(yaml_map: YamlMap, recursive_value_fn: F) -> Result, FormatToInternalMapError> - where - F: Fn(YamlValue) -> Result, 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 { - 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")] diff --git a/crates/rops/src/rops_file/format/yaml/tests.rs b/crates/rops/src/rops_file/format/yaml/tests.rs index 97bc3f0..8152749 100644 --- a/crates/rops/src/rops_file/format/yaml/tests.rs +++ b/crates/rops/src/rops_file/format/yaml/tests.rs @@ -1,9 +1,11 @@ mod transforms { mod to_internal { mod encrypted { + use std::fmt::Display; + use crate::*; - use super::helpers; + use super::helpers::KeyValueString; #[cfg(feature = "aes-gcm")] mod aes_gcm { @@ -27,7 +29,7 @@ mod transforms { escape_suffix.clone() => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(RopsValue::String("something".to_string()))), "encrypted".to_string() => RopsTree::Leaf(RopsMapEncryptedLeaf::Encrypted(EncryptedRopsValue::mock())) }), - helpers::create_format_map::>(&indoc::formatdoc! {" + YamlFileFormat::create_format_map::>(&indoc::formatdoc! {" {}: something encrypted: {}", escape_suffix, @@ -41,27 +43,27 @@ mod transforms { #[test] fn disallows_boolean_values_when_encrypted() { - assert_allowed_value_helper("disallowed_boolean: true") + assert_allowed_value_helper("disallowed_boolean", true) } #[test] fn disallows_integer_values_when_encrypted() { - assert_allowed_value_helper("disallowed_integer: 1") + assert_allowed_value_helper("disallowed_integer", 1) } #[test] fn disallows_non_string_keys() { assert!(matches!( - helpers::create_format_map::>("123: xxx") + YamlFileFormat::key_value_map::>(123, "xxx") .to_internal(None) .unwrap_err(), FormatToInternalMapError::NonStringKey(_) )) } - fn assert_allowed_value_helper(key_value_str: &str) { + fn assert_allowed_value_helper(key: impl Display, value: impl Display) { assert!(matches!( - helpers::create_format_map::>(key_value_str) + YamlFileFormat::key_value_map::>(key, value) .to_internal(None) .unwrap_err(), FormatToInternalMapError::PlaintextWhenEncrypted(_) @@ -72,7 +74,7 @@ mod transforms { mod decrypted { use crate::*; - use super::helpers; + use super::helpers::KeyValueString; #[test] fn transforms_decrypted_yaml_map() { @@ -85,7 +87,7 @@ mod transforms { #[test] fn disallows_non_string_keys() { assert!(matches!( - helpers::create_format_map::("123: xxx").to_internal().unwrap_err(), + YamlFileFormat::key_value_map::(123, "xxx").to_internal().unwrap_err(), FormatToInternalMapError::NonStringKey(_) )) } @@ -93,7 +95,7 @@ mod transforms { #[test] fn dissallows_out_of_range_integers() { assert!(matches!( - helpers::create_format_map::(&format!("invalid_integer: {}", u64::MAX)) + YamlFileFormat::key_value_map::("invalid_integer", u64::MAX) .to_internal() .unwrap_err(), FormatToInternalMapError::IntegerOutOfRange(_) @@ -102,10 +104,28 @@ mod transforms { } mod helpers { + use std::fmt::Display; + use crate::*; - pub fn create_format_map(key_value_str: &str) -> RopsFileFormatMap { - RopsFileFormatMap::::from_inner_map(serde_yaml::from_str::(key_value_str).unwrap()) + pub trait KeyValueString: FileFormat { + fn key_value_string(key: impl Display, value: impl Display) -> String; + + fn key_value_map(key: impl Display, value: impl Display) -> RopsFileFormatMap { + Self::create_format_map(&Self::key_value_string(key, value)) + } + + fn create_format_map(key_value_str: &str) -> RopsFileFormatMap; + } + + impl KeyValueString for YamlFileFormat { + fn key_value_string(key: impl Display, value: impl Display) -> String { + format!("{}: {}", key, value) + } + + fn create_format_map(key_value_str: &str) -> RopsFileFormatMap { + RopsFileFormatMap::from_inner_map(serde_yaml::from_str::(key_value_str).unwrap()) + } } } } diff --git a/crates/rops/src/rops_file/map/core.rs b/crates/rops/src/rops_file/map/core.rs index c70f45e..f134bd5 100644 --- a/crates/rops/src/rops_file/map/core.rs +++ b/crates/rops/src/rops_file/map/core.rs @@ -17,12 +17,12 @@ pub enum RopsTree { impl ToExternalMap> for RopsMap> { fn to_external(self) -> RopsFileFormatMap, F> { - RopsFileFormatMap::from_inner_map(F::encrypted_from_internal(self)) + RopsFileFormatMap::from_inner_map(F::Map::encrypted_internal_to_format_map(self)) } } impl ToExternalMap for RopsMap { fn to_external(self) -> RopsFileFormatMap { - RopsFileFormatMap::from_inner_map(F::decrypted_from_internal(self)) + RopsFileFormatMap::from_inner_map(F::Map::decrypted_internal_to_format(self)) } }