Skip to main content

rustfs_policy/policy/
function.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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                /* if inner_data.is_empty() {
149                    return Err(Error::custom("has no condition element"));
150                } */
151
152                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}