Files
rustfs/iam/src/policy/function.rs

394 lines
12 KiB
Rust

use crate::policy::function::condition::Condition;
use serde::ser::SerializeMap;
use serde::{de, Deserialize, Serialize, Serializer};
use std::collections::HashMap;
use std::collections::HashSet;
pub mod addr;
pub mod binary;
pub mod bool_null;
pub mod condition;
pub mod date;
pub mod func;
pub mod key;
pub mod key_name;
pub mod number;
pub mod string;
#[derive(Clone, Default, Debug)]
pub struct Functions {
for_any_value: Vec<Condition>,
for_all_values: Vec<Condition>,
for_normal: Vec<Condition>,
}
impl Functions {
pub fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
for c in self.for_any_value.iter() {
if !c.evaluate(false, values) {
return false;
}
}
for c in self.for_all_values.iter() {
if !c.evaluate(true, values) {
return false;
}
}
for c in self.for_normal.iter() {
if !c.evaluate(false, values) {
return false;
}
}
true
}
pub fn is_empty(&self) -> bool {
self.for_all_values.is_empty() && self.for_any_value.is_empty() && self.for_normal.is_empty()
}
}
impl Serialize for Functions {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut se =
serializer.serialize_map(Some(self.for_any_value.len() + self.for_all_values.len() + self.for_normal.len()))?;
for conditions in self.for_all_values.iter() {
se.serialize_key(format!("ForAllValues:{}", conditions.to_key()).as_str())?;
conditions.serialize_map(&mut se)?;
}
for conditions in self.for_any_value.iter() {
se.serialize_key(format!("ForAnyValue:{}", conditions.to_key()).as_str())?;
conditions.serialize_map(&mut se)?;
}
for conditions in self.for_normal.iter() {
se.serialize_key(conditions.to_key())?;
conditions.serialize_map(&mut se)?;
}
se.end()
}
}
impl<'de> Deserialize<'de> for Functions {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct FuncVisitor;
use serde::de::Visitor;
impl<'de> Visitor<'de> for FuncVisitor {
type Value = Functions;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("Functions")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
use serde::de::Error;
let mut hash = HashSet::with_capacity(map.size_hint().unwrap_or_default());
let mut inner_data = Functions::default();
while let Some(key) = map.next_key::<&str>()? {
if hash.contains(&key) {
return Err(Error::custom(format!("duplicate condition operator `{}`", key)));
}
hash.insert(key);
let mut tokens = key.split(":");
let mut qualifier = tokens.next();
let mut name = tokens.next();
if name.is_none() {
name = qualifier;
qualifier = None;
}
if tokens.next().is_some() {
return Err(Error::custom("invalid condition operator"));
}
let Some(name) = name else { return Err(Error::custom("has no condition operator")) };
let condition = Condition::from_deserializer(name, &mut map)?;
match qualifier {
Some("ForAnyValue") => inner_data.for_any_value.push(condition),
Some("ForAllValues") => inner_data.for_all_values.push(condition),
Some(q) => return Err(Error::custom(format!("invalid qualifier `{q}`"))),
None => inner_data.for_normal.push(condition),
}
}
/* if inner_data.is_empty() {
return Err(Error::custom("has no condition element"));
} */
Ok(inner_data)
}
}
deserializer.deserialize_map(FuncVisitor)
}
}
impl PartialEq for Functions {
fn eq(&self, other: &Self) -> bool {
if !(self.for_all_values.len() == other.for_all_values.len()
&& self.for_any_value.len() == other.for_any_value.len()
&& self.for_normal.len() == other.for_normal.len())
{
return false;
}
self.for_any_value.iter().all(|x| other.for_any_value.contains(x))
&& self.for_all_values.iter().all(|x| other.for_all_values.contains(x))
&& self.for_normal.iter().all(|x| other.for_normal.contains(x))
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Value;
#[cfg(test)]
mod tests {
use crate::policy::function::condition::Condition::*;
use crate::policy::function::func::FuncKeyValue;
use crate::policy::function::key::Key;
use crate::policy::function::string::StringFunc;
use crate::policy::function::string::StringFuncValue;
use crate::policy::Functions;
use test_case::test_case;
#[test_case(
r#"{
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": true
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": "true"
}
}"# => false; "1")]
#[test_case(r#"{}"# => true; "2")]
#[test_case(
r#"{
"StringLike": {
"s3:x-amz-metadata-directive": "REPL*"
},
"StringEquals": {
"s3:x-amz-copy-source": "mybucket/myobject"
},
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
},
"NotIpAddress": {
"aws:SourceIp": [
"10.1.10.0/24",
"10.10.1.0/24"
]
},
"StringNotLike": {
"s3:x-amz-storage-class": "STANDARD",
"s3:x-amz-server-side-encryption": "AES256"
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": true
},
"IpAddress": {
"aws:SourceIp": [
"192.168.1.0/24",
"192.168.2.0/24"
]
}
}"# => true; "3"
)]
#[test_case(
r#"{
"StringLike": {
"s3:x-amz-metadata-directive": "REPL*"
},
"StringEquals": {
"s3:x-amz-copy-source": "mybucket/myobject",
"s3:prefix": [
"",
"home/"
],
"s3:delimiter": [
"/"
]
},
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
},
"NotIpAddress": {
"aws:SourceIp": [
"10.1.10.0/24",
"10.10.1.0/24"
]
},
"StringNotLike": {
"s3:x-amz-storage-class": "STANDARD"
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": true
},
"IpAddress": {
"aws:SourceIp": [
"192.168.1.0/24",
"192.168.2.0/24"
]
}
}"# => true; "4"
)]
#[test_case(
r#"{
"IpAddress": {
"aws:SourceIp": [
"192.168.1.0/24"
]
},
"NotIpAddress": {
"aws:SourceIp": [
"10.1.10.0/24"
]
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": [
true
]
},
"StringEquals": {
"s3:x-amz-copy-source": [
"mybucket/myobject"
]
},
"StringLike": {
"s3:x-amz-metadata-directive": [
"REPL*"
]
},
"StringNotEquals": {
"s3:x-amz-server-side-encryption": [
"AES256"
]
},
"StringNotLike": {
"s3:x-amz-storage-class": [
"STANDARD"
]
}
}"# => true;
"5"
)]
#[test_case(
r#"{
"IpAddress": {
"aws:SourceIp": [
"192.168.1.0/24"
]
},
"NotIpAddress": {
"aws:SourceIp": [
"10.1.10.0/24"
]
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": [
true
]
},
"StringEquals": {
"s3:x-amz-copy-source": [
"mybucket/myobject"
]
},
"StringLike": {
"s3:x-amz-metadata-directive": [
"REPL*"
]
},
"StringNotEquals": {
"s3:x-amz-server-side-encryption": [
"aws:kms"
]
},
"StringNotLike": {
"s3:x-amz-storage-class": [
"STANDARD"
]
}
}"# => true;
"6"
)]
fn test_de(input: &str) -> bool {
serde_json::from_str::<Functions>(input)
.map_err(|e| eprintln!("{e:?}"))
.is_ok()
}
#[test_case(
Functions {
for_normal: vec![StringNotLike(StringFunc {
0: vec![FuncKeyValue {
key: Key::try_from("s3:LocationConstraint").unwrap(),
values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
}],
})],
..Default::default()
},
r#"{"StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#;
"1"
)]
#[test_case(
Functions {
for_all_values: vec![StringNotLike(StringFunc {
0: vec![FuncKeyValue {
key: Key::try_from("s3:LocationConstraint").unwrap(),
values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
}],
})],
..Default::default()
},
r#"{"ForAllValues:StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#;
"2"
)]
#[test_case(
Functions {
for_any_value: vec![StringNotLike(StringFunc {
0: vec![FuncKeyValue {
key: Key::try_from("s3:LocationConstraint").unwrap(),
values: StringFuncValue(vec!["us-east-1", "us-east-2"].into_iter().map(ToOwned::to_owned).collect()),
}],
})],
for_all_values: vec![StringNotLike(StringFunc {
0: vec![FuncKeyValue {
key: Key::try_from("s3:LocationConstraint").unwrap(),
values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
}],
})],
for_normal: vec![StringNotLike(StringFunc {
0: vec![FuncKeyValue {
key: Key::try_from("s3:LocationConstraint").unwrap(),
values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
}],
})],
},
r#"{"ForAllValues:StringNotLike":{"s3:LocationConstraint":"us-east-1"},"ForAnyValue:StringNotLike":{"s3:LocationConstraint":["us-east-1","us-east-2"]},"StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#;
"3"
)]
fn test_ser(input: Functions, expect: &str) {
assert_eq!(serde_json::to_string(&input).unwrap(), expect);
}
}