1use crate::policy::function::condition::Condition;
16use serde::ser::SerializeMap;
17use serde::{Deserialize, Serialize, Serializer, de};
18use std::collections::HashMap;
19use std::collections::HashSet;
20
21pub mod addr;
22pub mod binary;
23pub mod bool_null;
24pub mod condition;
25pub mod date;
26pub mod func;
27pub mod key;
28pub mod key_name;
29pub mod number;
30pub mod string;
31
32#[derive(Clone, Default, Debug)]
33pub struct Functions {
34 for_any_value: Vec<Condition>,
35 for_all_values: Vec<Condition>,
36 for_normal: Vec<Condition>,
37}
38
39impl Functions {
40 pub fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
41 for c in self.for_any_value.iter() {
42 if !c.evaluate(false, values) {
43 return false;
44 }
45 }
46
47 for c in self.for_all_values.iter() {
48 if !c.evaluate(true, values) {
49 return false;
50 }
51 }
52
53 for c in self.for_normal.iter() {
54 if !c.evaluate(false, values) {
55 return false;
56 }
57 }
58
59 true
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.for_all_values.is_empty() && self.for_any_value.is_empty() && self.for_normal.is_empty()
64 }
65}
66
67impl Serialize for Functions {
68 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69 where
70 S: Serializer,
71 {
72 let mut se =
73 serializer.serialize_map(Some(self.for_any_value.len() + self.for_all_values.len() + self.for_normal.len()))?;
74
75 for conditions in self.for_all_values.iter() {
76 se.serialize_key(format!("ForAllValues:{}", conditions.to_key()).as_str())?;
77 conditions.serialize_map(&mut se)?;
78 }
79
80 for conditions in self.for_any_value.iter() {
81 se.serialize_key(format!("ForAnyValue:{}", conditions.to_key()).as_str())?;
82 conditions.serialize_map(&mut se)?;
83 }
84
85 for conditions in self.for_normal.iter() {
86 se.serialize_key(conditions.to_key())?;
87 conditions.serialize_map(&mut se)?;
88 }
89
90 se.end()
91 }
92}
93
94impl<'de> Deserialize<'de> for Functions {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: serde::Deserializer<'de>,
98 {
99 struct FuncVisitor;
100 use serde::de::Visitor;
101
102 impl<'de> Visitor<'de> for FuncVisitor {
103 type Value = Functions;
104
105 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
106 formatter.write_str("Functions")
107 }
108
109 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
110 where
111 A: de::MapAccess<'de>,
112 {
113 use serde::de::Error;
114
115 let mut hash = HashSet::with_capacity(map.size_hint().unwrap_or_default());
116
117 let mut inner_data = Functions::default();
118 while let Some(key) = map.next_key::<&str>()? {
119 if hash.contains(&key) {
120 return Err(Error::custom(format!("duplicate condition operator `{key}`")));
121 }
122
123 hash.insert(key);
124
125 let mut tokens = key.split(":");
126 let mut qualifier = tokens.next();
127 let mut name = tokens.next();
128 if name.is_none() {
129 name = qualifier;
130 qualifier = None;
131 }
132
133 if tokens.next().is_some() {
134 return Err(Error::custom("invalid condition operator"));
135 }
136
137 let Some(name) = name else { return Err(Error::custom("has no condition operator")) };
138
139 let condition = Condition::from_deserializer(name, &mut map)?;
140 match qualifier {
141 Some("ForAnyValue") => inner_data.for_any_value.push(condition),
142 Some("ForAllValues") => inner_data.for_all_values.push(condition),
143 Some(q) => return Err(Error::custom(format!("invalid qualifier `{q}`"))),
144 None => inner_data.for_normal.push(condition),
145 }
146 }
147
148 Ok(inner_data)
153 }
154 }
155
156 deserializer.deserialize_map(FuncVisitor)
157 }
158}
159
160impl PartialEq for Functions {
161 fn eq(&self, other: &Self) -> bool {
162 if !(self.for_all_values.len() == other.for_all_values.len()
163 && self.for_any_value.len() == other.for_any_value.len()
164 && self.for_normal.len() == other.for_normal.len())
165 {
166 return false;
167 }
168
169 self.for_any_value.iter().all(|x| other.for_any_value.contains(x))
170 && self.for_all_values.iter().all(|x| other.for_all_values.contains(x))
171 && self.for_normal.iter().all(|x| other.for_normal.contains(x))
172 }
173}
174
175#[derive(Clone, Serialize, Deserialize)]
176pub struct Value;
177
178#[cfg(test)]
179mod tests {
180 use crate::policy::Functions;
181 use crate::policy::function::condition::Condition::*;
182 use crate::policy::function::func::FuncKeyValue;
183 use crate::policy::function::key::Key;
184 use crate::policy::function::string::StringFunc;
185 use crate::policy::function::string::StringFuncValue;
186 use test_case::test_case;
187
188 #[test_case(
189 r#"{
190 "Null": {
191 "s3:x-amz-server-side-encryption-customer-algorithm": true
192 },
193 "Null": {
194 "s3:x-amz-server-side-encryption-customer-algorithm": "true"
195 }
196 }"# => false; "1")]
197 #[test_case(r#"{}"# => true; "2")]
198 #[test_case(
199 r#"{
200 "StringLike": {
201 "s3:x-amz-metadata-directive": "REPL*"
202 },
203 "StringEquals": {
204 "s3:x-amz-copy-source": "mybucket/myobject"
205 },
206 "StringNotEquals": {
207 "s3:x-amz-server-side-encryption": "AES256"
208 },
209 "NotIpAddress": {
210 "aws:SourceIp": [
211 "10.1.10.0/24",
212 "10.10.1.0/24"
213 ]
214 },
215 "StringNotLike": {
216 "s3:x-amz-storage-class": "STANDARD",
217 "s3:x-amz-server-side-encryption": "AES256"
218 },
219 "Null": {
220 "s3:x-amz-server-side-encryption-customer-algorithm": true
221 },
222 "IpAddress": {
223 "aws:SourceIp": [
224 "192.168.1.0/24",
225 "192.168.2.0/24"
226 ]
227 }
228 }"# => true; "3"
229 )]
230 #[test_case(
231 r#"{
232 "StringLike": {
233 "s3:x-amz-metadata-directive": "REPL*"
234 },
235 "StringEquals": {
236 "s3:x-amz-copy-source": "mybucket/myobject",
237 "s3:prefix": [
238 "",
239 "home/"
240 ],
241 "s3:delimiter": [
242 "/"
243 ]
244 },
245 "StringNotEquals": {
246 "s3:x-amz-server-side-encryption": "AES256"
247 },
248 "NotIpAddress": {
249 "aws:SourceIp": [
250 "10.1.10.0/24",
251 "10.10.1.0/24"
252 ]
253 },
254 "StringNotLike": {
255 "s3:x-amz-storage-class": "STANDARD"
256 },
257 "Null": {
258 "s3:x-amz-server-side-encryption-customer-algorithm": true
259 },
260 "IpAddress": {
261 "aws:SourceIp": [
262 "192.168.1.0/24",
263 "192.168.2.0/24"
264 ]
265 }
266 }"# => true; "4"
267 )]
268 #[test_case(
269 r#"{
270 "IpAddress": {
271 "aws:SourceIp": [
272 "192.168.1.0/24"
273 ]
274 },
275 "NotIpAddress": {
276 "aws:SourceIp": [
277 "10.1.10.0/24"
278 ]
279 },
280 "Null": {
281 "s3:x-amz-server-side-encryption-customer-algorithm": [
282 true
283 ]
284 },
285 "StringEquals": {
286 "s3:x-amz-copy-source": [
287 "mybucket/myobject"
288 ]
289 },
290 "StringLike": {
291 "s3:x-amz-metadata-directive": [
292 "REPL*"
293 ]
294 },
295 "StringNotEquals": {
296 "s3:x-amz-server-side-encryption": [
297 "AES256"
298 ]
299 },
300 "StringNotLike": {
301 "s3:x-amz-storage-class": [
302 "STANDARD"
303 ]
304 }
305 }"# => true;
306 "5"
307 )]
308 #[test_case(
309 r#"{
310 "IpAddress": {
311 "aws:SourceIp": [
312 "192.168.1.0/24"
313 ]
314 },
315 "NotIpAddress": {
316 "aws:SourceIp": [
317 "10.1.10.0/24"
318 ]
319 },
320 "Null": {
321 "s3:x-amz-server-side-encryption-customer-algorithm": [
322 true
323 ]
324 },
325 "StringEquals": {
326 "s3:x-amz-copy-source": [
327 "mybucket/myobject"
328 ]
329 },
330 "StringLike": {
331 "s3:x-amz-metadata-directive": [
332 "REPL*"
333 ]
334 },
335 "StringNotEquals": {
336 "s3:x-amz-server-side-encryption": [
337 "aws:kms"
338 ]
339 },
340 "StringNotLike": {
341 "s3:x-amz-storage-class": [
342 "STANDARD"
343 ]
344 }
345 }"# => true;
346 "6"
347 )]
348 fn test_de(input: &str) -> bool {
349 serde_json::from_str::<Functions>(input)
350 .map_err(|e| eprintln!("{e:?}"))
351 .is_ok()
352 }
353
354 #[test_case(
355 Functions {
356 for_normal: vec![StringNotLike(StringFunc {
357 0: vec![FuncKeyValue {
358 key: Key::try_from("s3:LocationConstraint").unwrap(),
359 values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
360 }],
361 })],
362 ..Default::default()
363 },
364 r#"{"StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#;
365 "1"
366 )]
367 #[test_case(
368 Functions {
369 for_all_values: vec![StringNotLike(StringFunc {
370 0: vec![FuncKeyValue {
371 key: Key::try_from("s3:LocationConstraint").unwrap(),
372 values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
373 }],
374 })],
375 ..Default::default()
376 },
377 r#"{"ForAllValues:StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#;
378 "2"
379 )]
380 #[test_case(
381 Functions {
382 for_any_value: vec![StringNotLike(StringFunc {
383 0: vec![FuncKeyValue {
384 key: Key::try_from("s3:LocationConstraint").unwrap(),
385 values: StringFuncValue(vec!["us-east-1", "us-east-2"].into_iter().map(ToOwned::to_owned).collect()),
386 }],
387 })],
388 for_all_values: vec![StringNotLike(StringFunc {
389 0: vec![FuncKeyValue {
390 key: Key::try_from("s3:LocationConstraint").unwrap(),
391 values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
392 }],
393 })],
394 for_normal: vec![StringNotLike(StringFunc {
395 0: vec![FuncKeyValue {
396 key: Key::try_from("s3:LocationConstraint").unwrap(),
397 values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()),
398 }],
399 })],
400 },
401 r#"{"ForAllValues:StringNotLike":{"s3:LocationConstraint":"us-east-1"},"ForAnyValue:StringNotLike":{"s3:LocationConstraint":["us-east-1","us-east-2"]},"StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#;
402 "3"
403 )]
404 fn test_ser(input: Functions, expect: &str) {
405 assert_eq!(serde_json::to_string(&input).unwrap(), expect);
406 }
407}