mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 08:38:58 +00:00
test(iam): add policy_is_allowed
This commit is contained in:
@@ -108,7 +108,7 @@ async fn monitor_local_disks_and_heal() {
|
||||
info!("heal local disks: {:?}", heal_disks);
|
||||
|
||||
let store = new_object_layer_fn().expect("errServerNotInitialized");
|
||||
if let (result, Some(err)) = store.heal_format(false).await.expect("heal format failed") {
|
||||
if let (_result, Some(err)) = store.heal_format(false).await.expect("heal format failed") {
|
||||
error!("heal local disk format error: {}", err);
|
||||
if let Some(DiskError::NoHealRequired) = err.downcast_ref::<DiskError>() {
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::policy::{Policy, Validator};
|
||||
use crate::service_type::ServiceType;
|
||||
use crate::{utils, Error};
|
||||
use jsonwebtoken::{encode, Algorithm, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::cell::LazyCell;
|
||||
|
||||
+3
-3
@@ -4,8 +4,8 @@ mod effect;
|
||||
mod function;
|
||||
mod id;
|
||||
mod policy;
|
||||
mod resource;
|
||||
mod statement;
|
||||
pub mod resource;
|
||||
pub mod statement;
|
||||
pub(crate) mod utils;
|
||||
|
||||
use action::Action;
|
||||
@@ -17,7 +17,7 @@ pub use id::ID;
|
||||
pub use policy::{default::DEFAULT_POLICIES, Policy};
|
||||
pub use resource::ResourceSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{to_string, Value};
|
||||
use serde_json::Value;
|
||||
pub use statement::Statement;
|
||||
use std::collections::HashMap;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Validator for ActionSet {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, IntoStaticStr)]
|
||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone)]
|
||||
#[serde(try_from = "&str", untagged)]
|
||||
pub enum Action {
|
||||
S3Action(S3Action),
|
||||
@@ -55,11 +55,22 @@ impl Action {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Action> for &str {
|
||||
fn from(value: &Action) -> &'static str {
|
||||
match value {
|
||||
Action::S3Action(s) => s.into(),
|
||||
Action::AdminAction(s) => s.into(),
|
||||
Action::StsAction(s) => s.into(),
|
||||
Action::KmsAction(s) => s.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Action {
|
||||
const S3_PREFIX: &'static str = "s3:";
|
||||
const ADMIN_PREFIX: &str = "admin:";
|
||||
const STS_PREFIX: &str = "sts:";
|
||||
const KMS_PREFIX: &str = "kms:";
|
||||
const ADMIN_PREFIX: &'static str = "admin:";
|
||||
const STS_PREFIX: &'static str = "sts:";
|
||||
const KMS_PREFIX: &'static str = "kms:";
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Action {
|
||||
@@ -86,8 +97,10 @@ impl TryFrom<&str> for Action {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr)]
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
#[serde(try_from = "&str", into = "&str")]
|
||||
pub enum S3Action {
|
||||
#[cfg_attr(test, default)]
|
||||
#[strum(serialize = "s3:*")]
|
||||
AllActions,
|
||||
#[strum(serialize = "s3:GetBucketLocation")]
|
||||
|
||||
+246
-40
@@ -1,7 +1,8 @@
|
||||
use std::{collections::HashMap, ops::Deref};
|
||||
|
||||
use func::Func;
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
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;
|
||||
@@ -14,12 +15,65 @@ pub mod key_name;
|
||||
pub mod number;
|
||||
pub mod string;
|
||||
|
||||
#[derive(Clone, Default, Serialize)]
|
||||
pub struct Functions(pub Vec<Func>);
|
||||
#[derive(Clone, Default)]
|
||||
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 {
|
||||
self.0.iter().all(|x| x.evaluate(values))
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,30 +98,44 @@ impl<'de> Deserialize<'de> for Functions {
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
let inner_data = Vec::with_capacity(map.size_hint().unwrap_or(0));
|
||||
while let Some(key) = map.next_key::<&str>()? {
|
||||
let mut tokens = key.split(":");
|
||||
let name = tokens.next();
|
||||
let qualifier = tokens.next();
|
||||
let mut hash = HashSet::with_capacity(map.size_hint().unwrap_or_default());
|
||||
|
||||
// 多个:
|
||||
if tokens.next().is_some() {
|
||||
return Err(A::Error::custom("invalid codition"));
|
||||
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)));
|
||||
}
|
||||
|
||||
let Some(_name) = name else { return Err(A::Error::custom("invalid codition")) };
|
||||
hash.insert(key);
|
||||
|
||||
let f = match qualifier {
|
||||
Some("ForAnyValues") => Func::ForAnyValues,
|
||||
Some("ForAllValues") => Func::ForAllValues,
|
||||
Some(q) => return Err(A::Error::custom(format!("invalid qualifier `{q}`"))),
|
||||
None => Func::ForNormal,
|
||||
};
|
||||
let mut tokens = key.split(":");
|
||||
let mut qualifier = tokens.next();
|
||||
let mut name = tokens.next();
|
||||
if name.is_none() {
|
||||
name = qualifier;
|
||||
qualifier = None;
|
||||
}
|
||||
|
||||
// inner_data.push(f(name.try_into()?))
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Functions(inner_data))
|
||||
if inner_data.is_empty() {
|
||||
return Err(Error::custom("has no condition element"));
|
||||
}
|
||||
|
||||
Ok(inner_data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,21 +143,21 @@ impl<'de> Deserialize<'de> for Functions {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Functions {
|
||||
type Target = Vec<Func>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[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::key_name::KeyName;
|
||||
use crate::policy::function::string::StringFunc;
|
||||
use crate::policy::function::string::StringFuncValue;
|
||||
use crate::policy::Functions;
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case::test_case(
|
||||
#[test_case(
|
||||
r#"{
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": true
|
||||
@@ -97,9 +165,9 @@ mod tests {
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": "true"
|
||||
}
|
||||
}"# => true; "1")]
|
||||
#[test_case::test_case(r#"{}"# => true; "2")]
|
||||
#[test_case::test_case(
|
||||
}"# => false; "1")]
|
||||
#[test_case(r#"{}"# => false; "2")]
|
||||
#[test_case(
|
||||
r#"{
|
||||
"StringLike": {
|
||||
"s3:x-amz-metadata-directive": "REPL*"
|
||||
@@ -117,7 +185,8 @@ mod tests {
|
||||
]
|
||||
},
|
||||
"StringNotLike": {
|
||||
"s3:x-amz-storage-class": "STANDARD"
|
||||
"s3:x-amz-storage-class": "STANDARD",
|
||||
"s3:x-amz-server-side-encryption": "AES256"
|
||||
},
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": true
|
||||
@@ -130,7 +199,7 @@ mod tests {
|
||||
}
|
||||
}"# => true; "3"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
#[test_case(
|
||||
r#"{
|
||||
"StringLike": {
|
||||
"s3:x-amz-metadata-directive": "REPL*"
|
||||
@@ -168,7 +237,144 @@ mod tests {
|
||||
}
|
||||
}"# => true; "4"
|
||||
)]
|
||||
fn test_serde(input: &str) -> bool {
|
||||
true
|
||||
#[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()),
|
||||
}],
|
||||
})],
|
||||
..Default::default()
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,18 @@ pub type AddrFunc = InnerFunc<AddrFuncValue>;
|
||||
|
||||
impl AddrFunc {
|
||||
pub(crate) fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
let rvalues = values.get(self.key.name().as_str()).map(|t| t.iter()).unwrap_or_default();
|
||||
for inner in self.0.iter() {
|
||||
let rvalues = values.get(inner.key.name().as_str()).map(|t| t.iter()).unwrap_or_default();
|
||||
|
||||
for r in rvalues {
|
||||
let Ok(ip) = r.parse::<IpAddr>() else {
|
||||
return false;
|
||||
};
|
||||
for r in rvalues {
|
||||
let Ok(ip) = r.parse::<IpAddr>() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
for ip_net in self.values.0.iter() {
|
||||
if ip_net.contains(ip) {
|
||||
return true;
|
||||
for ip_net in inner.values.0.iter() {
|
||||
if ip_net.contains(ip) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +49,7 @@ impl<'de> Deserialize<'de> for AddrFuncValue {
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(AddrFuncValue(vec![Self::to_cidr::<E>(v)?]))
|
||||
Ok(AddrFuncValue(vec![Self::cidr::<E>(v)?]))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
@@ -57,7 +59,7 @@ impl<'de> Deserialize<'de> for AddrFuncValue {
|
||||
Ok(AddrFuncValue({
|
||||
let mut data = Vec::with_capacity(seq.size_hint().unwrap_or_default());
|
||||
while let Some(v) = seq.next_element::<&str>()? {
|
||||
data.push(Self::to_cidr::<A::Error>(v)?)
|
||||
data.push(Self::cidr::<A::Error>(v)?)
|
||||
}
|
||||
data
|
||||
}))
|
||||
@@ -65,7 +67,7 @@ impl<'de> Deserialize<'de> for AddrFuncValue {
|
||||
}
|
||||
|
||||
impl AddrFuncValueVisitor {
|
||||
fn to_cidr<E: serde::de::Error>(v: &str) -> Result<IpNetwork, E> {
|
||||
fn cidr<E: serde::de::Error>(v: &str) -> Result<IpNetwork, E> {
|
||||
let mut cidr_str = Cow::from(v);
|
||||
if v.find('/').is_none() {
|
||||
cidr_str.to_mut().push_str("/32");
|
||||
@@ -84,6 +86,7 @@ impl<'de> Deserialize<'de> for AddrFuncValue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AddrFunc, AddrFuncValue};
|
||||
use crate::policy::function::func::FuncKeyValue;
|
||||
use crate::policy::function::{
|
||||
key::Key,
|
||||
key_name::AwsKeyName::*,
|
||||
@@ -93,8 +96,10 @@ mod tests {
|
||||
|
||||
fn new_func(name: KeyName, variable: Option<String>, value: Vec<&str>) -> AddrFunc {
|
||||
AddrFunc {
|
||||
key: Key { name, variable },
|
||||
values: AddrFuncValue(value.into_iter().map(|x| x.parse().unwrap()).collect()),
|
||||
0: vec![FuncKeyValue {
|
||||
key: Key { name, variable },
|
||||
values: AddrFuncValue(value.into_iter().filter_map(|x| x.parse().ok()).collect()),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
use super::func::InnerFunc;
|
||||
use serde::de::{Error, IgnoredAny, SeqAccess};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
pub type BoolFunc = InnerFunc<BoolFuncValue>;
|
||||
impl BoolFunc {
|
||||
pub fn evaluate_bool(&self, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
match values.get(self.key.name().as_str()).and_then(|x| x.get(0)) {
|
||||
Some(x) => self.values.0.to_string().as_str() == x,
|
||||
None => false,
|
||||
for inner in self.0.iter() {
|
||||
if !match values.get(inner.key.name().as_str()).and_then(|x| x.get(0)) {
|
||||
Some(x) => inner.values.0.to_string().as_str() == x,
|
||||
None => false,
|
||||
} {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn evaluate_null(&self, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
let len = values.get(self.key.name().as_str()).map(Vec::len).unwrap_or(0);
|
||||
if self.values.0 {
|
||||
return len == 0;
|
||||
for inner in self.0.iter() {
|
||||
let len = values.get(inner.key.name().as_str()).map(Vec::len).unwrap_or(0);
|
||||
let r = if inner.values.0 { len == 0 } else { len != 0 };
|
||||
|
||||
if !r {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
len != 0
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,17 +61,32 @@ impl<'de> Deserialize<'de> for BoolFuncValue {
|
||||
|
||||
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
E: Error,
|
||||
{
|
||||
Ok(BoolFuncValue(value))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
E: Error,
|
||||
{
|
||||
Ok(BoolFuncValue(value.parse::<bool>().map_err(|e| E::custom(format!("{e:?}")))?))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let Some(v) = seq.next_element::<BoolFuncValue>()? else {
|
||||
return Err(Error::custom("no value for boolean"));
|
||||
};
|
||||
|
||||
if seq.next_element::<IgnoredAny>()?.is_some() {
|
||||
return Err(Error::custom("only allow one boolean value"));
|
||||
}
|
||||
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(BoolOrStringVisitor)
|
||||
@@ -70,6 +96,7 @@ impl<'de> Deserialize<'de> for BoolFuncValue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BoolFunc, BoolFuncValue};
|
||||
use crate::policy::function::func::FuncKeyValue;
|
||||
use crate::policy::function::{
|
||||
key::Key,
|
||||
key_name::AwsKeyName::*,
|
||||
@@ -79,8 +106,10 @@ mod tests {
|
||||
|
||||
fn new_func(name: KeyName, variable: Option<String>, value: bool) -> BoolFunc {
|
||||
BoolFunc {
|
||||
key: Key { name, variable },
|
||||
values: BoolFuncValue(value),
|
||||
0: vec![FuncKeyValue {
|
||||
key: Key { name, variable },
|
||||
values: BoolFuncValue(value),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +121,8 @@ mod tests {
|
||||
#[test_case(r#"{"aws:SecureTransport/a": "false"}"#, new_func(Aws(AWSSecureTransport), Some("a".into()), false); "10")]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": true}"#, new_func(Aws(AWSSecureTransport), Some("a".into()), true); "11")]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": false}"#, new_func(Aws(AWSSecureTransport), Some("a".into()), false); "12")]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": [true]}"#, new_func(Aws(AWSSecureTransport), Some("a".into()), true); "13")]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": ["false"]}"#, new_func(Aws(AWSSecureTransport), Some("a".into()), false); "14")]
|
||||
fn test_deser(input: &str, expect: BoolFunc) -> Result<(), serde_json::Error> {
|
||||
let v: BoolFunc = serde_json::from_str(input)?;
|
||||
assert_eq!(v, expect);
|
||||
@@ -103,6 +134,9 @@ mod tests {
|
||||
#[test_case(r#"{"aws:usernamea/value":"johndoe"}"#)]
|
||||
#[test_case(r#"{"aws:usernamea/value":["johndoe", "aaa"]}"#)]
|
||||
#[test_case(r#""aaa""#)]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": ["false", "true"]}"#)]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": [true, false]}"#)]
|
||||
#[test_case(r#"{"aws:SecureTransport/a": ["aa"]}"#)]
|
||||
fn test_deser_failed(input: &str) {
|
||||
assert!(serde_json::from_str::<BoolFunc>(input).is_err());
|
||||
}
|
||||
@@ -111,6 +145,7 @@ mod tests {
|
||||
#[test_case(r#"{"aws:SecureTransport":"false"}"#, new_func(Aws(AWSSecureTransport), None, false);"2")]
|
||||
#[test_case(r#"{"aws:SecureTransport/aa":"true"}"#, new_func(Aws(AWSSecureTransport),Some("aa".into()), true);"3")]
|
||||
#[test_case(r#"{"aws:SecureTransport/aa":"false"}"#, new_func(Aws(AWSSecureTransport), Some("aa".into()), false);"4")]
|
||||
# [test_case(r#"{"aws:SecureTransport/aa":"false"}"#, new_func(Aws(AWSSecureTransport), Some("aa".into()), false); "5")]
|
||||
fn test_ser(expect: &str, input: BoolFunc) -> Result<(), serde_json::Error> {
|
||||
let v = serde_json::to_string(&input)?;
|
||||
assert_eq!(v.as_str(), expect);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use serde::de::{Error, MapAccess};
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::{addr::AddrFunc, binary::BinaryFunc, bool_null::BoolFunc, date::DateFunc, number::NumberFunc, string::StringFunc};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub enum Condition {
|
||||
StringEquals(StringFunc),
|
||||
StringNotEquals(StringFunc),
|
||||
@@ -34,16 +35,73 @@ pub enum Condition {
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
pub fn from_deserializer<'a, D: MapAccess<'a>>(key: &str, d: &mut D) -> Result<Self, D::Error> {
|
||||
Ok(match key {
|
||||
"StringEquals" => Self::StringEquals(d.next_value()?),
|
||||
"StringNotEquals" => Self::StringNotEquals(d.next_value()?),
|
||||
"StringEqualsIgnoreCase" => Self::StringEqualsIgnoreCase(d.next_value()?),
|
||||
"StringNotEqualsIgnoreCase" => Self::StringNotEqualsIgnoreCase(d.next_value()?),
|
||||
"StringLike" => Self::StringLike(d.next_value()?),
|
||||
"StringNotLike" => Self::StringNotLike(d.next_value()?),
|
||||
"BinaryEquals" => Self::BinaryEquals(d.next_value()?),
|
||||
"IpAddress" => Self::IpAddress(d.next_value()?),
|
||||
"NotIpAddress" => Self::NotIpAddress(d.next_value()?),
|
||||
"Null" => Self::Null(d.next_value()?),
|
||||
"Bool" => Self::Bool(d.next_value()?),
|
||||
"NumericEquals" => Self::NumericEquals(d.next_value()?),
|
||||
"NumericNotEquals" => Self::NumericNotEquals(d.next_value()?),
|
||||
"NumericLessThan" => Self::NumericLessThan(d.next_value()?),
|
||||
"NumericGreaterThan" => Self::NumericGreaterThan(d.next_value()?),
|
||||
"NumericGreaterThanIfExists" => Self::NumericGreaterThanIfExists(d.next_value()?),
|
||||
"NumericGreaterThanEquals" => Self::NumericGreaterThanEquals(d.next_value()?),
|
||||
"DateEquals" => Self::DateEquals(d.next_value()?),
|
||||
"DateNotEquals" => Self::DateNotEquals(d.next_value()?),
|
||||
"DateLessThanEquals" => Self::DateLessThanEquals(d.next_value()?),
|
||||
"DateGreaterThan" => Self::DateGreaterThan(d.next_value()?),
|
||||
"DateGreaterThanEquals" => Self::DateGreaterThanEquals(d.next_value()?),
|
||||
_ => Err(Error::custom(format!("unknown key: {key}")))?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_key(&self) -> &'static str {
|
||||
match self {
|
||||
Condition::StringEquals(_) => "StringEquals",
|
||||
Condition::StringNotEquals(_) => "StringNotEquals",
|
||||
Condition::StringEqualsIgnoreCase(_) => "StringEqualsIgnoreCase",
|
||||
Condition::StringNotEqualsIgnoreCase(_) => "StringNotEqualsIgnoreCase",
|
||||
Condition::StringLike(_) => "StringLike",
|
||||
Condition::StringNotLike(_) => "StringNotLike",
|
||||
Condition::BinaryEquals(_) => "BinaryEquals",
|
||||
Condition::IpAddress(_) => "IpAddress",
|
||||
Condition::NotIpAddress(_) => "NotIpAddress",
|
||||
Condition::Null(_) => "Null",
|
||||
Condition::Bool(_) => "Bool",
|
||||
Condition::NumericEquals(_) => "NumericEquals",
|
||||
Condition::NumericNotEquals(_) => "NumericNotEquals",
|
||||
Condition::NumericLessThan(_) => "NumericLessThan",
|
||||
Condition::NumericLessThanEquals(_) => "NumericLessThanEquals",
|
||||
Condition::NumericGreaterThan(_) => "NumericGreaterThan",
|
||||
Condition::NumericGreaterThanIfExists(_) => "NumericGreaterThanIfExists",
|
||||
Condition::NumericGreaterThanEquals(_) => "NumericGreaterThanEquals",
|
||||
Condition::DateEquals(_) => "DateEquals",
|
||||
Condition::DateNotEquals(_) => "DateNotEquals",
|
||||
Condition::DateLessThan(_) => "DateLessThan",
|
||||
Condition::DateLessThanEquals(_) => "DateLessThanEquals",
|
||||
Condition::DateGreaterThan(_) => "DateGreaterThan",
|
||||
Condition::DateGreaterThanEquals(_) => "DateGreaterThanEquals",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, for_all: bool, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
use Condition::*;
|
||||
|
||||
let r = match self {
|
||||
StringEquals(s) => s.evaluate(for_all, false, false, values),
|
||||
StringNotEquals(s) => s.evaluate(for_all, false, false, values),
|
||||
StringEqualsIgnoreCase(s) => s.evaluate(for_all, true, false, values),
|
||||
StringNotEqualsIgnoreCase(s) => s.evaluate(for_all, true, false, values),
|
||||
StringLike(s) => s.evaluate(for_all, false, true, values),
|
||||
StringNotLike(s) => s.evaluate(for_all, false, true, values),
|
||||
StringEquals(s) => s.evaluate(for_all, false, false, false, values),
|
||||
StringNotEquals(s) => s.evaluate(for_all, false, false, true, values),
|
||||
StringEqualsIgnoreCase(s) => s.evaluate(for_all, true, false, false, values),
|
||||
StringNotEqualsIgnoreCase(s) => s.evaluate(for_all, true, false, true, values),
|
||||
StringLike(s) => s.evaluate(for_all, false, true, false, values),
|
||||
StringNotLike(s) => s.evaluate(for_all, false, true, true, values),
|
||||
BinaryEquals(s) => todo!(),
|
||||
IpAddress(s) => s.evaluate(values),
|
||||
NotIpAddress(s) => s.evaluate(values),
|
||||
@@ -71,8 +129,38 @@ impl Condition {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_negate(&self) -> bool {
|
||||
use Condition::*;
|
||||
matches!(self, StringNotEquals(_) | StringNotEqualsIgnoreCase(_) | NotIpAddress(_))
|
||||
}
|
||||
|
||||
pub fn serialize_map<T: SerializeMap>(&self, se: &mut T) -> Result<(), T::Error> {
|
||||
match self {
|
||||
Condition::StringEquals(s) => se.serialize_value(s),
|
||||
Condition::StringNotEquals(s) => se.serialize_value(s),
|
||||
Condition::StringEqualsIgnoreCase(s) => se.serialize_value(s),
|
||||
Condition::StringNotEqualsIgnoreCase(s) => se.serialize_value(s),
|
||||
Condition::StringLike(s) => se.serialize_value(s),
|
||||
Condition::StringNotLike(s) => se.serialize_value(s),
|
||||
Condition::BinaryEquals(s) => se.serialize_value(s),
|
||||
Condition::IpAddress(s) => se.serialize_value(s),
|
||||
Condition::NotIpAddress(s) => se.serialize_value(s),
|
||||
Condition::Null(s) => se.serialize_value(s),
|
||||
Condition::Bool(s) => se.serialize_value(s),
|
||||
Condition::NumericEquals(s) => se.serialize_value(s),
|
||||
Condition::NumericNotEquals(s) => se.serialize_value(s),
|
||||
Condition::NumericLessThan(s) => se.serialize_value(s),
|
||||
Condition::NumericLessThanEquals(s) => se.serialize_value(s),
|
||||
Condition::NumericGreaterThan(s) => se.serialize_value(s),
|
||||
Condition::NumericGreaterThanIfExists(s) => se.serialize_value(s),
|
||||
Condition::NumericGreaterThanEquals(s) => se.serialize_value(s),
|
||||
Condition::DateEquals(s) => se.serialize_value(s),
|
||||
Condition::DateNotEquals(s) => se.serialize_value(s),
|
||||
Condition::DateLessThan(s) => se.serialize_value(s),
|
||||
Condition::DateLessThanEquals(s) => se.serialize_value(s),
|
||||
Condition::DateGreaterThan(s) => se.serialize_value(s),
|
||||
Condition::DateGreaterThanEquals(s) => se.serialize_value(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,23 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
pub type DateFunc = InnerFunc<DateFuncValue>;
|
||||
|
||||
impl DateFunc {
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
op: impl FnOnce(&OffsetDateTime, &OffsetDateTime) -> bool,
|
||||
values: &HashMap<String, Vec<String>>,
|
||||
) -> bool {
|
||||
let v = match values.get(self.key.name().as_str()).and_then(|x| x.get(0)) {
|
||||
Some(x) => x,
|
||||
None => return false,
|
||||
};
|
||||
pub fn evaluate(&self, op: impl Fn(&OffsetDateTime, &OffsetDateTime) -> bool, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
for inner in self.0.iter() {
|
||||
let v = match values.get(inner.key.name().as_str()).and_then(|x| x.get(0)) {
|
||||
Some(x) => x,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let Ok(rv) = OffsetDateTime::parse(v, &Rfc3339) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(rv) = OffsetDateTime::parse(v, &Rfc3339) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
op(&self.values.0, &rv)
|
||||
if !op(&inner.values.0, &rv) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +76,7 @@ impl<'de> Deserialize<'de> for DateFuncValue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DateFunc, DateFuncValue};
|
||||
use crate::policy::function::func::FuncKeyValue;
|
||||
use crate::policy::function::{
|
||||
key::Key,
|
||||
key_name::KeyName::{self, *},
|
||||
@@ -84,8 +87,10 @@ mod tests {
|
||||
|
||||
fn new_func(name: KeyName, variable: Option<String>, value: &str) -> DateFunc {
|
||||
DateFunc {
|
||||
key: Key { name, variable },
|
||||
values: DateFuncValue(OffsetDateTime::parse(value, &Rfc3339).unwrap()),
|
||||
0: vec![FuncKeyValue {
|
||||
key: Key { name, variable },
|
||||
values: DateFuncValue(OffsetDateTime::parse(value, &Rfc3339).unwrap()),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,22 @@
|
||||
use std::{collections::HashMap, marker::PhantomData};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use serde::{
|
||||
de::{self, Visitor},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
|
||||
use super::{condition::Condition, key::Key};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum Func {
|
||||
ForAnyValues(Vec<Condition>),
|
||||
ForAllValues(Vec<Condition>),
|
||||
ForNormal(Vec<Condition>),
|
||||
}
|
||||
|
||||
impl Func {
|
||||
pub fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
match self {
|
||||
Self::ForAnyValues(conditions) => conditions.iter().all(|x| x.evaluate(true, values)),
|
||||
Self::ForAllValues(conditions) => conditions.iter().all(|x| x.evaluate(false, values)),
|
||||
Self::ForNormal(conditions) => conditions.iter().all(|x| x.evaluate(false, values)),
|
||||
}
|
||||
}
|
||||
}
|
||||
use super::key::Key;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
pub struct InnerFunc<T> {
|
||||
pub struct InnerFunc<T>(pub(crate) Vec<FuncKeyValue<T>>);
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
pub struct FuncKeyValue<T> {
|
||||
pub key: Key,
|
||||
pub values: T,
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for InnerFunc<T> {
|
||||
impl<T: Clone> Clone for FuncKeyValue<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
key: self.key.clone(),
|
||||
@@ -39,6 +25,12 @@ impl<T: Clone> Clone for InnerFunc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for InnerFunc<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> Serialize for InnerFunc<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
@@ -46,9 +38,13 @@ impl<T: Serialize> Serialize for InnerFunc<T> {
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(1))?;
|
||||
map.serialize_key(&self.key)?;
|
||||
map.serialize_value(&self.values)?;
|
||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
||||
|
||||
for kv in self.0.iter() {
|
||||
map.serialize_key(&kv.key)?;
|
||||
map.serialize_value(&kv.values)?;
|
||||
}
|
||||
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
@@ -78,11 +74,16 @@ where
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
let Some((key, values)) = map.next_entry::<Key, T>()? else {
|
||||
return Err(A::Error::custom("no k-v pair"));
|
||||
};
|
||||
let mut inner = Vec::with_capacity(map.size_hint().unwrap_or(0));
|
||||
while let Some((key, values)) = map.next_entry::<Key, T>()? {
|
||||
inner.push(FuncKeyValue { key, values });
|
||||
}
|
||||
|
||||
Ok(InnerFunc { key, values })
|
||||
if inner.is_empty() {
|
||||
return Err(Error::custom("has no condition key"));
|
||||
}
|
||||
|
||||
Ok(InnerFunc(inner))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::key_name::KeyName;
|
||||
use crate::policy::{Error, Validator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
@@ -19,8 +18,8 @@ impl Key {
|
||||
self.name.eq(other)
|
||||
}
|
||||
|
||||
pub fn val_name(&self) -> String {
|
||||
self.name.val_name()
|
||||
pub fn var_name(&self) -> String {
|
||||
self.name.var_name()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
@@ -34,7 +33,12 @@ impl Key {
|
||||
|
||||
impl From<Key> for String {
|
||||
fn from(value: Key) -> Self {
|
||||
value.name()
|
||||
let mut data = String::from(Into::<&str>::into(&value.name));
|
||||
if let Some(x) = value.variable.as_ref() {
|
||||
data.push('/');
|
||||
data.push_str(&x);
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ impl TryFrom<&str> for KeyName {
|
||||
}
|
||||
|
||||
impl KeyName {
|
||||
pub const COMMON_KEYS: &[KeyName] = &[
|
||||
pub const COMMON_KEYS: &'static [KeyName] = &[
|
||||
// s3
|
||||
KeyName::S3(S3KeyName::S3SignatureVersion),
|
||||
KeyName::S3(S3KeyName::S3AuthType),
|
||||
@@ -82,8 +82,36 @@ impl KeyName {
|
||||
KeyName::Jwt(JwtKeyName::JWTClientID),
|
||||
];
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
pub const fn prefix(&self) -> usize {
|
||||
match self {
|
||||
KeyName::Aws(_) => "aws:".len(),
|
||||
KeyName::Jwt(_) => "jwt:".len(),
|
||||
KeyName::Ldap(_) => "ldap:".len(),
|
||||
KeyName::Sts(_) => "sts:".len(),
|
||||
KeyName::Svc(_) => "svc:".len(),
|
||||
KeyName::S3(_) => "s3:".len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&Into::<&str>::into(self)[self.prefix()..]
|
||||
}
|
||||
|
||||
pub fn var_name(&self) -> String {
|
||||
match self {
|
||||
KeyName::Aws(s) => format!("${{aws:{}}}", Into::<&str>::into(s)),
|
||||
KeyName::Jwt(s) => format!("${{jwt:{}}}", Into::<&str>::into(s)),
|
||||
KeyName::Ldap(s) => format!("${{ldap:{}}}", Into::<&str>::into(s)),
|
||||
KeyName::Sts(s) => format!("${{sts:{}}}", Into::<&str>::into(s)),
|
||||
KeyName::Svc(s) => format!("${{svc:{}}}", Into::<&str>::into(s)),
|
||||
KeyName::S3(s) => format!("${{s3:{}}}", Into::<&str>::into(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KeyName> for &'static str {
|
||||
fn from(k: &KeyName) -> Self {
|
||||
match k {
|
||||
KeyName::Aws(aws) => aws.into(),
|
||||
KeyName::Jwt(jwt) => jwt.into(),
|
||||
KeyName::Ldap(ldap) => ldap.into(),
|
||||
@@ -92,17 +120,6 @@ impl KeyName {
|
||||
KeyName::S3(s3) => s3.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn val_name(&self) -> String {
|
||||
match self {
|
||||
KeyName::Aws(aws) => Into::<&str>::into(aws).to_owned(),
|
||||
KeyName::Jwt(jwt) => Into::<&str>::into(jwt).to_owned(),
|
||||
KeyName::Ldap(ldap) => Into::<&str>::into(ldap).to_owned(),
|
||||
KeyName::Sts(sts) => Into::<&str>::into(sts).to_owned(),
|
||||
KeyName::Svc(svc) => Into::<&str>::into(svc).to_owned(),
|
||||
KeyName::S3(s3) => Into::<&str>::into(s3).to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, EnumString, Debug, IntoStaticStr, Eq, PartialEq, Serialize, Deserialize)]
|
||||
@@ -137,6 +154,21 @@ pub enum S3KeyName {
|
||||
|
||||
#[strum(serialize = "s3:max-keys")]
|
||||
S3MaxKeys,
|
||||
|
||||
#[strum(serialize = "s3:x-amz-metadata-directive")]
|
||||
S3XAmzMetadataDirective,
|
||||
|
||||
#[strum(serialize = "s3:x-amz-storage-class")]
|
||||
S3XAmzStorageClass,
|
||||
|
||||
#[strum(serialize = "s3:prefix")]
|
||||
S3Prefix,
|
||||
|
||||
#[strum(serialize = "s3:delimiter")]
|
||||
S3Delimiter,
|
||||
|
||||
#[strum(serialize = "s3:ExistingObjectTag")]
|
||||
S3ExistingObjectTag,
|
||||
}
|
||||
|
||||
#[derive(Clone, EnumString, Debug, IntoStaticStr, Eq, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -13,17 +13,23 @@ pub type NumberFunc = InnerFunc<NumberFuncValue>;
|
||||
pub struct NumberFuncValue(i64);
|
||||
|
||||
impl NumberFunc {
|
||||
pub fn evaluate(&self, op: impl FnOnce(&i64, &i64) -> bool, if_exists: bool, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
let v = match values.get(self.key.name().as_str()).and_then(|x| x.get(0)) {
|
||||
Some(x) => x,
|
||||
None => return if_exists,
|
||||
};
|
||||
pub fn evaluate(&self, op: impl Fn(&i64, &i64) -> bool, if_exists: bool, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
for inner in self.0.iter() {
|
||||
let v = match values.get(inner.key.name().as_str()).and_then(|x| x.get(0)) {
|
||||
Some(x) => x,
|
||||
None => return if_exists,
|
||||
};
|
||||
|
||||
let Ok(rv) = v.parse::<i64>() else {
|
||||
return false;
|
||||
};
|
||||
let Ok(rv) = v.parse::<i64>() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
op(&rv, &self.values.0)
|
||||
if !op(&rv, &inner.values.0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,13 +56,6 @@ impl<'de> Deserialize<'de> for NumberFuncValue {
|
||||
formatter.write_str("a number or a string that can be represented as a number.")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(NumberFuncValue(value.parse().map_err(|e| E::custom(format!("{e:?}")))?))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
@@ -70,6 +69,13 @@ impl<'de> Deserialize<'de> for NumberFuncValue {
|
||||
{
|
||||
Ok(NumberFuncValue(value as i64))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(NumberFuncValue(value.parse().map_err(|e| E::custom(format!("{e:?}")))?))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(NumberVisitor)
|
||||
@@ -79,6 +85,7 @@ impl<'de> Deserialize<'de> for NumberFuncValue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{NumberFunc, NumberFuncValue};
|
||||
use crate::policy::function::func::FuncKeyValue;
|
||||
use crate::policy::function::{
|
||||
key::Key,
|
||||
key_name::KeyName::{self, *},
|
||||
@@ -88,8 +95,10 @@ mod tests {
|
||||
|
||||
fn new_func(name: KeyName, variable: Option<String>, value: i64) -> NumberFunc {
|
||||
NumberFunc {
|
||||
key: Key { name, variable },
|
||||
values: NumberFuncValue(value),
|
||||
0: vec![FuncKeyValue {
|
||||
key: Key { name, variable },
|
||||
values: NumberFuncValue(value),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,43 @@ use std::collections::HashSet as Set;
|
||||
use std::fmt;
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use serde::{de, ser::SerializeSeq, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::policy::function::func::FuncKeyValue;
|
||||
use crate::policy::utils::wildcard;
|
||||
use serde::{de, ser::SerializeSeq, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::{func::InnerFunc, key_name::KeyName};
|
||||
|
||||
pub type StringFunc = InnerFunc<StringFuncValue>;
|
||||
|
||||
impl StringFunc {
|
||||
pub(crate) fn evaluate(
|
||||
&self,
|
||||
for_all: bool,
|
||||
ignore_case: bool,
|
||||
like: bool,
|
||||
negate: bool,
|
||||
values: &HashMap<String, Vec<String>>,
|
||||
) -> bool {
|
||||
for inner in self.0.iter() {
|
||||
let result = if like {
|
||||
inner.eval_like(for_all, values) ^ negate
|
||||
} else {
|
||||
inner.eval(for_all, ignore_case, values) ^ negate
|
||||
};
|
||||
|
||||
if !result {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl FuncKeyValue<StringFuncValue> {
|
||||
fn eval(&self, for_all: bool, ignore_case: bool, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
let rvalues = values
|
||||
// http.CanonicalHeaderKey ?
|
||||
.get(self.key.name().as_str())
|
||||
.map(|t| {
|
||||
t.iter()
|
||||
@@ -38,7 +64,7 @@ impl StringFunc {
|
||||
let mut c = Cow::from(c);
|
||||
for key in KeyName::COMMON_KEYS {
|
||||
match values.get(key.name()).and_then(|x| x.get(0)) {
|
||||
Some(v) if !v.is_empty() => return Cow::Owned(c.to_mut().replace(key.name(), v)),
|
||||
Some(v) if !v.is_empty() => return Cow::Owned(c.to_mut().replace(&key.var_name(), v)),
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
@@ -49,6 +75,7 @@ impl StringFunc {
|
||||
.collect::<Set<_>>();
|
||||
|
||||
let ivalues = rvalues.intersection(&fvalues);
|
||||
|
||||
if for_all {
|
||||
rvalues.is_empty() || rvalues.len() == ivalues.count()
|
||||
} else {
|
||||
@@ -67,7 +94,7 @@ impl StringFunc {
|
||||
let mut c = Cow::from(c);
|
||||
for key in KeyName::COMMON_KEYS {
|
||||
match values.get(key.name()).and_then(|x| x.get(0)) {
|
||||
Some(v) if !v.is_empty() => return Cow::Owned(c.to_mut().replace(key.name(), v)),
|
||||
Some(v) if !v.is_empty() => return Cow::Owned(c.to_mut().replace(&key.var_name(), v)),
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
@@ -88,20 +115,12 @@ impl StringFunc {
|
||||
|
||||
for_all
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate(&self, for_all: bool, ignore_case: bool, like: bool, values: &HashMap<String, Vec<String>>) -> bool {
|
||||
if like {
|
||||
self.eval_like(for_all, values)
|
||||
} else {
|
||||
self.eval(for_all, ignore_case, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析values字段
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
pub struct StringFuncValue(Set<String>);
|
||||
pub struct StringFuncValue(pub Set<String>);
|
||||
|
||||
impl Serialize for StringFuncValue {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@@ -165,7 +184,7 @@ impl<'d> Deserialize<'d> for StringFuncValue {
|
||||
if result.0.is_empty() {
|
||||
use serde::de::Error;
|
||||
|
||||
return Err(D::Error::custom("empty"));
|
||||
return Err(Error::custom("empty"));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
@@ -175,24 +194,34 @@ impl<'d> Deserialize<'d> for StringFuncValue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{StringFunc, StringFuncValue};
|
||||
use crate::policy::function::func::FuncKeyValue;
|
||||
use crate::policy::function::{
|
||||
key::Key,
|
||||
key_name::AwsKeyName::*,
|
||||
key_name::KeyName::{self, *},
|
||||
};
|
||||
|
||||
use crate::policy::function::key_name::S3KeyName::S3LocationConstraint;
|
||||
use test_case::test_case;
|
||||
|
||||
fn new_func(name: KeyName, variable: Option<String>, values: Vec<&str>) -> StringFunc {
|
||||
StringFunc {
|
||||
key: Key { name, variable },
|
||||
values: StringFuncValue(values.into_iter().map(|x| x.to_owned()).collect()),
|
||||
0: vec![FuncKeyValue {
|
||||
key: Key { name, variable },
|
||||
values: StringFuncValue(values.into_iter().map(|x| x.to_owned()).collect()),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(r#"{"aws:username": "johndoe"}"#, new_func(Aws(AWSUsername), None, vec!["johndoe"]))]
|
||||
#[test_case(r#"{"aws:username": ["johndoe", "aaa"]}"#, new_func(Aws(AWSUsername), None, vec!["johndoe", "aaa"]))]
|
||||
#[test_case(r#"{"aws:username/value": "johndoe"}"#, new_func(Aws(AWSUsername), Some("value".into()), vec!["johndoe"]))]
|
||||
#[test_case(r#"{"aws:username/value": ["johndoe", "aaa"]}"#, new_func(Aws(AWSUsername), Some("value".into()), vec!["johndoe", "aaa"]))]
|
||||
#[test_case(r#"{"aws:username": "johndoe"}"#,
|
||||
new_func(Aws(AWSUsername), None, vec!["johndoe"])
|
||||
)]
|
||||
#[test_case(r#"{"aws:username": ["johndoe", "aaa"]}"#, new_func(Aws(AWSUsername), None, vec!["johndoe", "aaa"]
|
||||
))]
|
||||
#[test_case(r#"{"aws:username/value": "johndoe"}"#, new_func(Aws(AWSUsername), Some("value".into()), vec!["johndoe"]
|
||||
))]
|
||||
#[test_case(r#"{"aws:username/value": ["johndoe", "aaa"]}"#, new_func(Aws(AWSUsername), Some("value".into()), vec!["johndoe", "aaa"]
|
||||
))]
|
||||
fn test_deser(input: &str, expect: StringFunc) -> Result<(), serde_json::Error> {
|
||||
let v: StringFunc = serde_json::from_str(input)?;
|
||||
assert_eq!(v, expect);
|
||||
@@ -217,4 +246,178 @@ mod tests {
|
||||
assert_eq!(v.as_str(), expect);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_fkv(name: &str, values: Vec<&str>) -> FuncKeyValue<StringFuncValue> {
|
||||
FuncKeyValue {
|
||||
key: name.try_into().unwrap(),
|
||||
values: StringFuncValue(values.into_iter().map(ToOwned::to_owned).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
fn test_eval(
|
||||
s: FuncKeyValue<StringFuncValue>,
|
||||
for_all: bool,
|
||||
ignore_case: bool,
|
||||
negate: bool,
|
||||
values: Vec<(&str, Vec<&str>)>,
|
||||
) -> bool {
|
||||
let result = s.eval(
|
||||
for_all,
|
||||
ignore_case,
|
||||
&values
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_owned(), v.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>()))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
result ^ negate
|
||||
}
|
||||
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["yourbucket/myobject"])] => false ; "2")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![] => false ; "3")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("delimiter", vec!["/"])] => false ; "4")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-1"])] => true ; "5")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["ap-southeast-1"])] => true ; "6")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["us-east-1"])] => false ; "7")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![] => false ; "8")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("delimiter", vec!["/"])] => false ; "9")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![("groups", vec!["prod", "art"])] => true ; "10")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![("groups", vec!["art"])] => true ; "11")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![] => true ; "12")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![("delimiter", vec!["/"])] => true ; "13")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![("groups", vec!["prod", "art"])] => true ; "14")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![("groups", vec!["art"])] => true ; "15")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![] => false ; "16")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![("delimiter", vec!["/"])] => false ; "17")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec![KeyName::S3(S3LocationConstraint).var_name().as_str()]), false, vec![("LocationConstraint", vec!["us-west-1"])] => true ; "18")]
|
||||
#[test_case(new_fkv("s3:ExistingObjectTag/security", vec!["public"]), false, vec![("ExistingObjectTag/security", vec!["public"])] => true ; "19")]
|
||||
#[test_case(new_fkv("s3:ExistingObjectTag/security", vec!["public"]), false, vec![("ExistingObjectTag/security", vec!["private"])] => false ; "20")]
|
||||
#[test_case(new_fkv("s3:ExistingObjectTag/security", vec!["public"]), false, vec![("ExistingObjectTag/project", vec!["foo"])] => false ; "21")]
|
||||
fn test_string_equals(s: FuncKeyValue<StringFuncValue>, for_all: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||
test_eval(s, for_all, false, false, values)
|
||||
}
|
||||
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => false ; "1")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["yourbucket/myobject"])] => true ; "2")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![] => true ; "3")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("delimiter", vec!["/"])] => true ; "4")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-1"])] => false ; "5")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["ap-southeast-1"])] => false ; "6")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["us-east-1"])] => true ; "7")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![] => true ; "8")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-1", "ap-southeast-1"]), false, vec![("delimiter", vec!["/"])] => true ; "9")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![("groups", vec!["prod", "art"])] => false ; "10")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![("groups", vec!["art"])] => false ; "11")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![] => false ; "12")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), true, vec![("delimiter", vec!["/"])] => false ; "13")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![("groups", vec!["prod", "art"])] => false ; "14")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![("groups", vec!["art"])] => false ; "15")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![] => true ; "16")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art"]), false, vec![("delimiter", vec!["/"])] => true ; "17")]
|
||||
fn test_string_not_equals(s: FuncKeyValue<StringFuncValue>, for_all: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||
test_eval(s, for_all, false, true, values)
|
||||
}
|
||||
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![("x-amz-copy-source", vec!["yourbucket/myobject"])] => false ; "2")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![] => false ; "3")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![("delimiter", vec!["/"])] => false ; "4")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-1"])] => true ; "5")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("LocationConstraint", vec!["ap-southeast-1"])] => true ; "6")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("LocationConstraint", vec!["us-east-1"])] => false ; "7")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![] => false ; "8")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("delimiter", vec!["/"])] => false ; "9")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![("groups", vec!["prod", "art"])] => true ; "10")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![("groups", vec!["art"])] => true ; "11")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![] => true ; "12")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![("delimiter", vec!["/"])] => true ; "13")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![("groups", vec!["prod", "art"])] => true ; "14")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![("groups", vec!["art"])] => true ; "15")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![] => false ; "16")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![("delimiter", vec!["/"])] => false ; "17")]
|
||||
fn test_string_equals_ignore_case(s: FuncKeyValue<StringFuncValue>, for_all: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||
test_eval(s, for_all, true, false, values)
|
||||
}
|
||||
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => false ; "1")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![("x-amz-copy-source", vec!["yourbucket/myobject"])] => true ; "2")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![] => true ; "3")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/MYOBJECT"]), false, vec![("delimiter", vec!["/"])] => true ; "4")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-1"])] => false ; "5")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("LocationConstraint", vec!["ap-southeast-1"])] => false ; "6")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("LocationConstraint", vec!["us-east-1"])] => true ; "7")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![] => true ; "8")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["EU-WEST-1", "AP-southeast-1"]), false, vec![("delimiter", vec!["/"])] => true ; "9")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![("groups", vec!["prod", "art"])] => false ; "10")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![("groups", vec!["art"])] => false ; "11")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![] => false ; "12")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), true, vec![("delimiter", vec!["/"])] => false ; "13")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![("groups", vec!["prod", "art"])] => false ; "14")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![("groups", vec!["art"])] => false ; "15")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![] => true ; "16")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["Prod", "Art"]), false, vec![("delimiter", vec!["/"])] => true ; "17")]
|
||||
fn test_string_not_equals_ignore_case(
|
||||
s: FuncKeyValue<StringFuncValue>,
|
||||
for_all: bool,
|
||||
values: Vec<(&str, Vec<&str>)>,
|
||||
) -> bool {
|
||||
test_eval(s, for_all, true, true, values)
|
||||
}
|
||||
|
||||
fn test_eval_like(s: FuncKeyValue<StringFuncValue>, for_all: bool, negate: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||
let result = s.eval_like(
|
||||
for_all,
|
||||
&values
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_owned(), v.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>()))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
result ^ negate
|
||||
}
|
||||
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["yourbucket/myobject"])] => false ; "2")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![] => false ; "3")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("delimiter", vec!["/"])] => false ; "4")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-1"])] => true ; "5")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["ap-southeast-1"])] => true ; "6")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["us-east-1"])] => false ; "7")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![] => false ; "8")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("delimiter", vec!["/"])] => false ; "9")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-2"])] => true ; "10")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![("groups", vec!["prod", "art"])] => true ; "11")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![("groups", vec!["art"])] => true ; "12")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![] => true ; "13")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![("delimiter", vec!["/"])] => true ; "14")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![("groups", vec!["prod", "art"])] => true ; "15")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![("groups", vec!["art"])] => true ; "16")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![] => false ; "17")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![("delimiter", vec!["/"])] => false ; "18")]
|
||||
fn test_string_like(s: FuncKeyValue<StringFuncValue>, for_all: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||
test_eval_like(s, for_all, false, values)
|
||||
}
|
||||
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => false ; "1")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["yourbucket/myobject"])] => true ; "2")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![] => true ; "3")]
|
||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("delimiter", vec!["/"])] => true ; "4")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-1"])] => false ; "5")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["ap-southeast-1"])] => false ; "6")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["us-east-1"])] => true ; "7")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![] => true ; "8")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("delimiter", vec!["/"])] => true ; "9")]
|
||||
#[test_case(new_fkv("s3:LocationConstraint", vec!["eu-west-*", "ap-southeast-1"]), false, vec![("LocationConstraint", vec!["eu-west-2"])] => false ; "10")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![("groups", vec!["prod", "art"])] => false ; "11")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![("groups", vec!["art"])] => false ; "12")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![] => false ; "13")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod", "art*"]), true, vec![("delimiter", vec!["/"])] => false ; "14")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![("groups", vec!["prod", "art"])] => false ; "15")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![("groups", vec!["art"])] => false ; "16")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![] => true ; "17")]
|
||||
#[test_case(new_fkv("jwt:groups", vec!["prod*", "art"]), false, vec![("delimiter", vec!["/"])] => true ; "18")]
|
||||
fn test_string_not_like(s: FuncKeyValue<StringFuncValue>, for_all: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||
test_eval_like(s, for_all, true, values)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -23,7 +23,7 @@ impl Policy {
|
||||
|
||||
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
|
||||
if statement.is_allowed(args) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +72,12 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet({
|
||||
resources: ResourceSet({
|
||||
let mut hash_set = HashSet::new();
|
||||
hash_set.insert(Resource::S3("*".into()));
|
||||
hash_set
|
||||
}),
|
||||
conditions: Functions(vec![]),
|
||||
conditions: Functions::default(),
|
||||
}],
|
||||
},
|
||||
),
|
||||
@@ -96,12 +96,12 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet({
|
||||
resources: ResourceSet({
|
||||
let mut hash_set = HashSet::new();
|
||||
hash_set.insert(Resource::S3("*".into()));
|
||||
hash_set
|
||||
}),
|
||||
conditions: Functions(vec![]),
|
||||
conditions: Functions::default(),
|
||||
}],
|
||||
},
|
||||
),
|
||||
@@ -119,12 +119,12 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet({
|
||||
resources: ResourceSet({
|
||||
let mut hash_set = HashSet::new();
|
||||
hash_set.insert(Resource::S3("*".into()));
|
||||
hash_set
|
||||
}),
|
||||
conditions: Functions(vec![]),
|
||||
conditions: Functions::default(),
|
||||
}],
|
||||
},
|
||||
),
|
||||
@@ -142,12 +142,12 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet({
|
||||
resources: ResourceSet({
|
||||
let mut hash_set = HashSet::new();
|
||||
hash_set.insert(Resource::S3("*".into()));
|
||||
hash_set
|
||||
}),
|
||||
conditions: Functions(vec![]),
|
||||
conditions: Functions::default(),
|
||||
}],
|
||||
},
|
||||
),
|
||||
@@ -172,12 +172,12 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet({
|
||||
resources: ResourceSet({
|
||||
let mut hash_set = HashSet::new();
|
||||
hash_set.insert(Resource::S3("*".into()));
|
||||
hash_set
|
||||
}),
|
||||
conditions: Functions(vec![]),
|
||||
conditions: Functions::default(),
|
||||
}],
|
||||
},
|
||||
),
|
||||
@@ -196,8 +196,8 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet(HashSet::new()),
|
||||
conditions: Functions(vec![]),
|
||||
resources: ResourceSet(HashSet::new()),
|
||||
conditions: Functions::default(),
|
||||
},
|
||||
Statement {
|
||||
sid: "".into(),
|
||||
@@ -208,8 +208,8 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet(HashSet::new()),
|
||||
conditions: Functions(vec![]),
|
||||
resources: ResourceSet(HashSet::new()),
|
||||
conditions: Functions::default(),
|
||||
},
|
||||
Statement {
|
||||
sid: "".into(),
|
||||
@@ -220,12 +220,12 @@ pub mod default {
|
||||
hash_set
|
||||
}),
|
||||
not_actions: ActionSet(Default::default()),
|
||||
resoures: ResourceSet({
|
||||
resources: ResourceSet({
|
||||
let mut hash_set = HashSet::new();
|
||||
hash_set.insert(Resource::S3("*".into()));
|
||||
hash_set
|
||||
}),
|
||||
conditions: Functions(vec![]),
|
||||
conditions: Functions::default(),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -52,19 +52,18 @@ pub enum Resource {
|
||||
}
|
||||
|
||||
impl Resource {
|
||||
pub const S3_PREFIX: &str = "arn:aws:s3:::";
|
||||
pub const S3_PREFIX: &'static str = "arn:aws:s3:::";
|
||||
|
||||
pub fn is_match(&self, resource: &str, conditons: &HashMap<String, Vec<String>>) -> bool {
|
||||
let mut pattern = match self {
|
||||
Resource::S3(s) => s.to_owned(),
|
||||
Resource::Kms(s) => s.to_owned(),
|
||||
};
|
||||
|
||||
if !conditons.is_empty() {
|
||||
for key in KeyName::COMMON_KEYS {
|
||||
if let Some(rvalue) = conditons.get(key.name()) {
|
||||
if matches!(rvalue.first().map(|c| !c.is_empty()), Some(true)) {
|
||||
pattern = pattern.replace(key.name(), &rvalue[0]);
|
||||
pattern = pattern.replace(&key.var_name(), &rvalue[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +82,7 @@ impl TryFrom<&str> for Resource {
|
||||
type Error = Error;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
let resource = if value.starts_with(Self::S3_PREFIX) {
|
||||
Resource::S3(value[Self::S3_PREFIX.len() + 1..].into())
|
||||
Resource::S3(value[Self::S3_PREFIX.len()..].into())
|
||||
} else {
|
||||
return Err(Error::InvalidResource("unknown".into(), value.into()));
|
||||
};
|
||||
@@ -116,3 +115,30 @@ impl Validator for Resource {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::policy::resource::Resource;
|
||||
use std::collections::HashMap;
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case("arn:aws:s3:::*","mybucket" => true; "1")]
|
||||
#[test_case("arn:aws:s3:::*","mybucket/myobject" => true; "2")]
|
||||
#[test_case("arn:aws:s3:::mybucket*","mybucket" => true; "3")]
|
||||
#[test_case("arn:aws:s3:::mybucket*","mybucket/myobject" => true; "4")]
|
||||
#[test_case("arn:aws:s3:::*/*","mybucket/myobject"=> true; "5")]
|
||||
#[test_case("arn:aws:s3:::mybucket/*","mybucket/myobject" => true; "6")]
|
||||
#[test_case("arn:aws:s3:::mybucket*/myobject","mybucket/myobject" => true; "7")]
|
||||
#[test_case("arn:aws:s3:::mybucket*/myobject","mybucket100/myobject" => true; "8")]
|
||||
#[test_case("arn:aws:s3:::mybucket?0/2010/photos/*","mybucket20/2010/photos/1.jpg" => true; "9")]
|
||||
#[test_case("arn:aws:s3:::mybucket","mybucket" => true; "10")]
|
||||
#[test_case("arn:aws:s3:::mybucket?0","mybucket30" => true; "11")]
|
||||
#[test_case("arn:aws:s3:::*/*","mybucket" => false; "12")]
|
||||
#[test_case("arn:aws:s3:::mybucket/*","mybucket10/myobject" => false; "13")]
|
||||
#[test_case("arn:aws:s3:::mybucket?0/2010/photos/*","mybucket0/2010/photos/1.jpg" => false; "14")]
|
||||
#[test_case("arn:aws:s3:::mybucket","mybucket/myobject" => false; "15")]
|
||||
fn test_resource_is_match(resource: &str, object: &str) -> bool {
|
||||
let resource: Resource = resource.try_into().unwrap();
|
||||
resource.is_match(object, &HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct Statement {
|
||||
pub effect: Effect,
|
||||
pub actions: ActionSet,
|
||||
pub not_actions: ActionSet,
|
||||
pub resoures: ResourceSet,
|
||||
pub resources: ResourceSet,
|
||||
pub conditions: Functions,
|
||||
}
|
||||
|
||||
@@ -61,12 +61,12 @@ impl Statement {
|
||||
}
|
||||
|
||||
if self.is_kms() {
|
||||
if resource == "/" || self.resoures.is_empty() {
|
||||
if resource == "/" || self.resources.is_empty() {
|
||||
break 'c self.conditions.evaluate(&args.conditions);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.resoures.is_match(&resource, &args.conditions) && !self.is_admin() && !self.is_sts() {
|
||||
if !self.resources.is_match(&resource, &args.conditions) && !self.is_admin() && !self.is_sts() {
|
||||
break 'c false;
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ impl Validator for Statement {
|
||||
return Err(Error::NonAction);
|
||||
}
|
||||
|
||||
if self.resoures.is_empty() {
|
||||
if self.resources.is_empty() {
|
||||
return Err(Error::NonResource);
|
||||
}
|
||||
|
||||
self.actions.is_valid()?;
|
||||
self.not_actions.is_valid()?;
|
||||
self.resoures.is_valid()?;
|
||||
self.resources.is_valid()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ where
|
||||
text.as_ref().len() <= pattern.as_ref().len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inner_match(pattern: impl AsRef<str>, name: impl AsRef<str>, simple: bool) -> bool {
|
||||
let (pattern, name) = (pattern.as_ref(), name.as_ref());
|
||||
|
||||
@@ -49,10 +50,10 @@ fn inner_match(pattern: impl AsRef<str>, name: impl AsRef<str>, simple: bool) ->
|
||||
return true;
|
||||
}
|
||||
|
||||
deep_match(name.as_bytes(), pattern.as_bytes(), simple)
|
||||
deep_match(pattern.as_bytes(), name.as_bytes(), simple)
|
||||
}
|
||||
|
||||
fn deep_match(mut name: &[u8], mut pattern: &[u8], simple: bool) -> bool {
|
||||
fn deep_match(mut pattern: &[u8], mut name: &[u8], simple: bool) -> bool {
|
||||
while !pattern.is_empty() {
|
||||
match pattern[0] {
|
||||
b'?' => {
|
||||
@@ -63,8 +64,8 @@ fn deep_match(mut name: &[u8], mut pattern: &[u8], simple: bool) -> bool {
|
||||
|
||||
b'*' => {
|
||||
return pattern.len() == 1
|
||||
|| deep_match(name, &pattern[1..], simple)
|
||||
|| (!name.is_empty() && deep_match(&name[1..], pattern, simple));
|
||||
|| deep_match(&pattern[1..], name, simple)
|
||||
|| (!name.is_empty() && deep_match(pattern, &name[1..], simple));
|
||||
}
|
||||
|
||||
_ => {
|
||||
@@ -137,6 +138,7 @@ mod tests {
|
||||
#[test_case::test_case("my-bucket/mnop*?and", "my-bucket/mnopqanda" => false; "50")]
|
||||
#[test_case::test_case("my-?-bucket/abc*", "my-bucket/mnopqanda" => false; "51")]
|
||||
#[test_case::test_case("a?", "a" => false; "52")]
|
||||
#[test_case::test_case("*", "mybucket/myobject" => true; "53")]
|
||||
fn test_is_match(pattern: &str, text: &str) -> bool {
|
||||
is_match(pattern, text)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use ecstore::{
|
||||
use futures::future::try_join_all;
|
||||
use log::{debug, warn};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use tracing::error;
|
||||
|
||||
use super::Store;
|
||||
use crate::{
|
||||
|
||||
@@ -0,0 +1,615 @@
|
||||
use iam::policy::action::Action;
|
||||
use iam::policy::action::ActionSet;
|
||||
use iam::policy::action::S3Action::*;
|
||||
use iam::policy::resource::ResourceSet;
|
||||
use iam::policy::Effect::*;
|
||||
use iam::policy::{Args, Policy, Statement, DEFAULT_VERSION};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use test_case::test_case;
|
||||
|
||||
#[derive(Default)]
|
||||
struct ArgsBuilder {
|
||||
pub account: String,
|
||||
pub groups: Vec<String>,
|
||||
pub action: String,
|
||||
pub bucket: String,
|
||||
pub conditions: HashMap<String, Vec<String>>,
|
||||
pub is_owner: bool,
|
||||
pub object: String,
|
||||
pub claims: HashMap<String, Value>,
|
||||
pub deny_only: bool,
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => true;
|
||||
"1"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => true;
|
||||
"2"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"3"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => true;
|
||||
"4"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => true;
|
||||
"5"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"6"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"7"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => true;
|
||||
"8"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => true;
|
||||
"9"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"10"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => true;
|
||||
"11"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => true;
|
||||
"12"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"13"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => true;
|
||||
"14"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"15"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"16"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => true;
|
||||
"17"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Allow,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"18"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Deny,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"19"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Deny,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => false;
|
||||
"20"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Deny,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"21"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Deny,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetBucketLocation".into(),
|
||||
bucket: "mybucket".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"22"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Deny,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:PutObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
conditions: {
|
||||
let mut h = HashMap::new();
|
||||
h.insert("x-amz-copy-source".into(), vec!["mybucket/myobject".into()]);
|
||||
h.insert("SourceIp".into(), vec!["192.168.1.10".into()]);
|
||||
h
|
||||
},
|
||||
..Default::default()
|
||||
} => false;
|
||||
"23"
|
||||
)]
|
||||
#[test_case(
|
||||
Policy{
|
||||
version: DEFAULT_VERSION.into(),
|
||||
statements: vec![
|
||||
Statement{
|
||||
effect: Deny,
|
||||
actions: ActionSet(vec![Action::S3Action(GetObjectAction), Action::S3Action(PutObjectAction)].into_iter().collect()),
|
||||
resources: ResourceSet(vec!["arn:aws:s3:::mybucket/myobject*".try_into().unwrap()].into_iter().collect()),
|
||||
conditions: serde_json::from_str(r#"{"IpAddress": {"aws:SourceIp": "192.168.1.0/24"}}"#).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
ArgsBuilder{
|
||||
account: "Q3AM3UQ867SPQQA43P2F".into(),
|
||||
action: "s3:GetObject".into(),
|
||||
bucket: "mybucket".into(),
|
||||
object: "myobject".into(),
|
||||
..Default::default()
|
||||
} => false;
|
||||
"24"
|
||||
)]
|
||||
fn policy_is_allowed(policy: Policy, args: ArgsBuilder) -> bool {
|
||||
policy.is_allowed(&Args {
|
||||
account: &args.account,
|
||||
groups: &args.groups,
|
||||
action: args.action.as_str().try_into().unwrap(),
|
||||
bucket: &args.bucket,
|
||||
conditions: &args.conditions,
|
||||
is_owner: args.is_owner,
|
||||
object: &args.object,
|
||||
claims: &args.claims,
|
||||
deny_only: args.deny_only,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user