aws_iam/model/
builder.rs

1/*!
2Provides a convenient and fluent builder interface for constructing policies.
3
4# Example
5
6```rust
7use aws_iam::model::*;
8use aws_iam::model::builder::*;
9use aws_iam::io::write_to_writer;
10use std::io::stdout;
11
12let policy: Policy = PolicyBuilder::new()
13    .named("confidential-data-access")
14    .evaluate_statement(
15        StatementBuilder::new()
16            .auto_named()
17            .allows()
18            .unspecified_principals()
19            .may_perform_actions(vec!["s3:List*", "s3:Get*"])
20            .on_resources(vec![
21                "arn:aws:s3:::confidential-data",
22                "arn:aws:s3:::confidential-data/_*",
23            ])
24            .if_condition(
25                ConditionBuilder::new_bool()
26                    .right_hand_bool("aws:MultiFactorAuthPresent", true)
27                    .if_exists(),
28            ),
29    )
30    .into();
31write_to_writer(stdout(), &policy);
32```
33*/
34
35use crate::model::*;
36use std::collections::HashMap;
37
38// ------------------------------------------------------------------------------------------------
39// Public Types
40// ------------------------------------------------------------------------------------------------
41
42///
43/// The top-level `Policy` builder.
44///
45#[derive(Debug, Default)]
46pub struct PolicyBuilder {
47    version: Option<Version>,
48    id: Option<String>,
49    statements: Vec<Statement>,
50}
51
52///
53/// A `Statement` builder, used with `PolicyBuilder::evaluate_statement()`.
54///
55#[derive(Debug, Clone)]
56pub struct StatementBuilder {
57    sid: Option<String>,
58    effect: Effect,
59    principals: HashMap<PrincipalType, Vec<String>>,
60    p_direction: Option<bool>,
61    actions: Vec<QString>,
62    a_direction: Option<bool>,
63    resources: Vec<String>,
64    r_direction: Option<bool>,
65    condition: Option<HashMap<ConditionOperator, HashMap<QString, OneOrAll<ConditionValue>>>>,
66}
67
68///
69/// A `Condition` builder, used with `StatementBuilder::if_condition()`.
70#[derive(Debug)]
71pub struct ConditionBuilder {
72    operator: ConditionOperator,
73    rhs: HashMap<QString, OneOrAll<ConditionValue>>,
74}
75
76// ------------------------------------------------------------------------------------------------
77// Implementations
78// ------------------------------------------------------------------------------------------------
79
80impl PolicyBuilder {
81    /// Create a new, empty, policy builder
82    pub fn new() -> Self {
83        Default::default()
84    }
85
86    /// Set the version of this policy.
87    pub fn version(&mut self, version: Version) -> &mut Self {
88        self.version = Some(version);
89        self
90    }
91
92    /// Use the IAM default for the version of this policy
93    pub fn default_version(&mut self) -> &mut Self {
94        self.version = Some(Policy::default_version());
95        self
96    }
97
98    /// Set the id of this policy
99    pub fn named(&mut self, id: &str) -> &mut Self {
100        self.id = Some(id.to_string());
101        self
102    }
103
104    /// Set the id of this policy to a randomly generate value.
105    pub fn auto_named(&mut self) -> &mut Self {
106        self.id = Some(Policy::new_id());
107        self
108    }
109
110    /// Add a statement to this policy.
111    pub fn evaluate_statement(&mut self, statement: &mut StatementBuilder) -> &mut Self {
112        self.statements.push(statement.into());
113        self
114    }
115
116    /// Add a list of statements to this policy.
117    pub fn evaluate_statements(&mut self, statements: &mut [StatementBuilder]) -> &mut Self {
118        self.statements.extend(
119            statements
120                .iter_mut()
121                .map(|sb| sb.into())
122                .collect::<Vec<Statement>>(),
123        );
124        self
125    }
126}
127
128impl From<&mut PolicyBuilder> for Policy {
129    fn from(pb: &mut PolicyBuilder) -> Self {
130        Policy {
131            version: pb.version.clone(),
132            id: pb.id.clone(),
133            statement: match pb.statements.len() {
134                0 => panic!("no statements!"),
135                1 => OneOrAll::One(pb.statements.remove(0)),
136                _ => OneOrAll::All(pb.statements.drain(0..).collect()),
137            },
138        }
139    }
140}
141
142impl Default for StatementBuilder {
143    fn default() -> Self {
144        StatementBuilder {
145            sid: None,
146            effect: Effect::Deny,
147            principals: HashMap::new(),
148            p_direction: None,
149            actions: Vec::new(),
150            a_direction: None,
151            resources: Vec::new(),
152            r_direction: None,
153            condition: None,
154        }
155    }
156}
157impl StatementBuilder {
158    /// Create a new, empty, statement builder
159    pub fn new() -> Self {
160        Default::default()
161    }
162
163    /// Set the id of this statement
164    pub fn named(&mut self, sid: &str) -> &mut Self {
165        self.sid = Some(sid.to_string());
166        self
167    }
168
169    /// Set the id of this statement to a randomly generate value.
170    pub fn auto_named(&mut self) -> &mut Self {
171        self.sid = Some(Statement::new_sid());
172        self
173    }
174
175    /// Set the effect of this statement to `Allow`.
176    pub fn allows(&mut self) -> &mut Self {
177        self.effect = Effect::Allow;
178        self
179    }
180
181    /// Set the effect of this statement to `Deny`.
182    pub fn does_not_allow(&mut self) -> &mut Self {
183        self.effect = Effect::Deny;
184        self
185    }
186
187    /// Unsets the principal associated with this statement
188    pub fn unspecified_principals(&mut self) -> &mut Self {
189        self.principals.clear();
190        self
191    }
192
193    /// Sets the principal of this statement to be a wildcard.
194    pub fn any_principal(&mut self, p_type: PrincipalType) -> &mut Self {
195        self.p_direction = Some(true);
196        self.principals.insert(p_type, Vec::new());
197        self
198    }
199
200    /// Sets the principal of this statement to be only this value.
201    pub fn only_this_principal(&mut self, p_type: PrincipalType, arn: &str) -> &mut Self {
202        self.only_these_principals(p_type, vec![arn]);
203        self
204    }
205
206    /// Sets the principal of this statement to be any of these values.
207    pub fn only_these_principals(&mut self, p_type: PrincipalType, arns: Vec<&str>) -> &mut Self {
208        match self.p_direction {
209            None => self.p_direction = Some(true),
210            Some(false) => panic!("you can't have principal *and* not principal"),
211            _ => (),
212        };
213        let existing = self.principals.entry(p_type).or_default();
214        existing.extend(arns.iter().map(|s| s.to_string()).collect::<Vec<String>>());
215        self
216    }
217
218    /// Sets the principal of this statement to exclude this value.
219    pub fn not_this_principal(&mut self, p_type: PrincipalType, arn: &str) -> &mut Self {
220        self.not_these_principals(p_type, vec![arn]);
221        self
222    }
223
224    /// Sets the principal of this statement to exclude of these values.
225    pub fn not_these_principals(&mut self, p_type: PrincipalType, arns: Vec<&str>) -> &mut Self {
226        match self.p_direction {
227            None => self.p_direction = Some(false),
228            Some(true) => panic!("you can't have principal *and* not principal"),
229            _ => (),
230        };
231        let existing = self.principals.entry(p_type).or_default();
232        existing.extend(arns.iter().map(|s| s.to_string()).collect::<Vec<String>>());
233        self
234    }
235
236    /// Sets the action of this statement to be a wildcard.
237    pub fn may_perform_any_action(&mut self) -> &mut Self {
238        self.a_direction = Some(true);
239        self.actions = Vec::new();
240        self
241    }
242
243    /// Sets the action of this statement to be only this value.
244    pub fn may_perform_action(&mut self, action: &str) -> &mut Self {
245        self.may_perform_actions(vec![action]);
246        self
247    }
248
249    /// Sets the action of this statement to be any of these values.
250    pub fn may_perform_actions(&mut self, actions: Vec<&str>) -> &mut Self {
251        match self.a_direction {
252            None => self.a_direction = Some(true),
253            Some(false) => panic!("you can't have action *and* not action"),
254            _ => (),
255        };
256        self.actions.extend(
257            actions
258                .iter()
259                .map(|s| s.parse().unwrap())
260                .collect::<Vec<QString>>(),
261        );
262        self
263    }
264
265    /// Sets the action of this statement to exclude the wildcard.
266    pub fn may_perform_no_action(&mut self) -> &mut Self {
267        self.a_direction = Some(false);
268        self.actions = Vec::new();
269        self
270    }
271
272    /// Sets the action of this statement to exclude this value.
273    pub fn may_not_perform_action(&mut self, action: &str) -> &mut Self {
274        self.may_not_perform_actions(vec![action]);
275        self
276    }
277
278    /// Sets the action of this statement to exclude any of these values.
279    pub fn may_not_perform_actions(&mut self, actions: Vec<&str>) -> &mut Self {
280        match self.a_direction {
281            None => self.a_direction = Some(false),
282            Some(true) => panic!("you can't have action *and* not action"),
283            _ => (),
284        };
285        self.actions.extend(
286            actions
287                .iter()
288                .map(|s| s.parse().unwrap())
289                .collect::<Vec<QString>>(),
290        );
291        self
292    }
293
294    /// Sets the resource of this statement to be a wildcard.
295    pub fn on_any_resource(&mut self) -> &mut Self {
296        self.r_direction = Some(true);
297        self.resources = Vec::new();
298        self
299    }
300
301    /// Sets the resource of this statement to be only this value.
302    pub fn on_resource(&mut self, resource: &str) -> &mut Self {
303        self.on_resources(vec![resource]);
304        self
305    }
306
307    /// Sets the resource of this statement to be any of these values.
308    pub fn on_resources(&mut self, resources: Vec<&str>) -> &mut Self {
309        match self.r_direction {
310            None => self.r_direction = Some(true),
311            Some(false) => panic!("you can't have resource *and* not resource"),
312            _ => (),
313        };
314        self.resources.extend(
315            resources
316                .iter()
317                .map(|s| s.to_string())
318                .collect::<Vec<String>>(),
319        );
320        self
321    }
322
323    /// Sets the resource of this statement to exclude the wildcard.
324    pub fn on_no_resource(&mut self) -> &mut Self {
325        self.r_direction = Some(false);
326        self.resources = Vec::new();
327        self
328    }
329
330    /// Sets the resource of this statement to exclude this value.
331    pub fn not_on_resource(&mut self, resource: &str) -> &mut Self {
332        self.not_on_resources(vec![resource]);
333        self
334    }
335
336    /// Sets the resource of this statement to exclude any of these values.
337    pub fn not_on_resources(&mut self, resources: Vec<&str>) -> &mut Self {
338        match self.r_direction {
339            None => self.r_direction = Some(false),
340            Some(true) => panic!("you can't have resource *and* not resource"),
341            _ => (),
342        };
343        self.resources.extend(
344            resources
345                .iter()
346                .map(|s| s.to_string())
347                .collect::<Vec<String>>(),
348        );
349        self
350    }
351
352    /// Adds this condition to the statement.
353    pub fn if_condition(&mut self, condition: &mut ConditionBuilder) -> &mut Self {
354        if self.condition.is_none() {
355            self.condition = Some(HashMap::new());
356        }
357        let conditions = self.condition.as_mut().unwrap();
358        let existing = conditions.entry(condition.operator.clone()).or_default();
359        existing.extend(condition.rhs.drain());
360        self
361    }
362}
363
364impl From<&mut StatementBuilder> for Statement {
365    fn from(sb: &mut StatementBuilder) -> Self {
366        let principal = match sb.p_direction {
367            None => None,
368            Some(direction) => {
369                let inner: HashMap<PrincipalType, OneOrAny> = sb
370                    .principals
371                    .iter_mut()
372                    .map(|(k, v)| {
373                        (
374                            k.clone(),
375                            match v.len() {
376                                0 => OneOrAny::Any,
377                                1 => OneOrAny::One(v.remove(0)),
378                                _ => OneOrAny::AnyOf(v.drain(0..).collect()),
379                            },
380                        )
381                    })
382                    .collect();
383                Some(if direction {
384                    Principal::Principal(inner)
385                } else {
386                    Principal::NotPrincipal(inner)
387                })
388            }
389        };
390
391        let action_inner = match sb.actions.len() {
392            0 => OneOrAny::Any,
393            1 => OneOrAny::One(sb.actions.remove(0)),
394            _ => OneOrAny::AnyOf(sb.actions.drain(0..).collect()),
395        };
396        let action = match sb.a_direction {
397            None => panic!("must have an action"),
398            Some(true) => Action::Action(action_inner),
399            Some(false) => Action::NotAction(action_inner),
400        };
401
402        let resource_inner = match sb.resources.len() {
403            0 => OneOrAny::Any,
404            1 => OneOrAny::One(sb.resources.remove(0)),
405            _ => OneOrAny::AnyOf(sb.resources.drain(0..).collect()),
406        };
407        let resource = match sb.r_direction {
408            None => panic!("must have a resource"),
409            Some(true) => Resource::Resource(resource_inner),
410            Some(false) => Resource::NotResource(resource_inner),
411        };
412
413        Statement {
414            sid: sb.sid.clone(),
415            principal,
416            effect: sb.effect.clone(),
417            action,
418            resource,
419            condition: sb.condition.clone(),
420        }
421    }
422}
423
424impl ConditionBuilder {
425    /// Create a new Condition with the provided operator.
426    pub fn new(operator: GlobalConditionOperator) -> Self {
427        ConditionBuilder {
428            operator: ConditionOperator {
429                quantifier: None,
430                operator,
431                if_exists: false,
432            },
433            rhs: Default::default(),
434        }
435    }
436
437    /// Create a new Condition with operator = `StringEquals`
438    pub fn new_string_equals() -> Self {
439        ConditionBuilder {
440            operator: ConditionOperator {
441                quantifier: None,
442                operator: GlobalConditionOperator::StringEquals,
443                if_exists: false,
444            },
445            rhs: Default::default(),
446        }
447    }
448
449    /// Create a new Condition with operator = `StringNotEquals`
450    pub fn new_string_not_equals() -> Self {
451        ConditionBuilder {
452            operator: ConditionOperator {
453                quantifier: None,
454                operator: GlobalConditionOperator::StringNotEquals,
455                if_exists: false,
456            },
457            rhs: Default::default(),
458        }
459    }
460
461    /// Create a new Condition with operator = `NumericEquals`
462    pub fn new_numeric_equals() -> Self {
463        ConditionBuilder {
464            operator: ConditionOperator {
465                quantifier: None,
466                operator: GlobalConditionOperator::NumericEquals,
467                if_exists: false,
468            },
469            rhs: Default::default(),
470        }
471    }
472
473    /// Create a new Condition with operator = `NumericNotEquals`
474    pub fn new_numeric_not_equals() -> Self {
475        ConditionBuilder {
476            operator: ConditionOperator {
477                quantifier: None,
478                operator: GlobalConditionOperator::NumericNotEquals,
479                if_exists: false,
480            },
481            rhs: Default::default(),
482        }
483    }
484
485    /// Create a new Condition with operator = `Bool`
486    pub fn new_bool() -> Self {
487        ConditionBuilder {
488            operator: ConditionOperator {
489                quantifier: None,
490                operator: GlobalConditionOperator::Bool,
491                if_exists: false,
492            },
493            rhs: Default::default(),
494        }
495    }
496
497    /// Add the _for-all-values_ quantifier.
498    pub fn for_all(&mut self) -> &mut Self {
499        self.operator.quantifier = Some(ConditionOperatorQuantifier::ForAllValues);
500        self
501    }
502
503    /// Add the _for-any-value_ quantifier.
504    pub fn for_any(&mut self) -> &mut Self {
505        self.operator.quantifier = Some(ConditionOperatorQuantifier::ForAnyValue);
506        self
507    }
508
509    /// Add a list of values to the _right-hand-sidse_ of this condition.
510    pub fn right_hand_side(&mut self, key: &str, values: &mut Vec<ConditionValue>) -> &mut Self {
511        let values = match values.len() {
512            0 => panic!("you must specify at least one value"),
513            1 => OneOrAll::One(values.remove(0)),
514            _ => OneOrAll::All(values.drain(0..).collect()),
515        };
516        self.rhs.insert(key.parse().unwrap(), values);
517        self
518    }
519
520    /// Add a string value to the _right-hand-sidse_ of this condition.
521    pub fn right_hand_str(&mut self, key: &str, value: &str) -> &mut Self {
522        self.rhs.insert(
523            key.parse().unwrap(),
524            OneOrAll::One(ConditionValue::String(value.to_string())),
525        );
526        self
527    }
528
529    /// Add a integer value to the _right-hand-sidse_ of this condition.
530    pub fn right_hand_int(&mut self, key: &str, value: i64) -> &mut Self {
531        self.rhs.insert(
532            key.parse().unwrap(),
533            OneOrAll::One(ConditionValue::Integer(value)),
534        );
535        self
536    }
537
538    /// Add a float value to the _right-hand-sidse_ of this condition.
539    pub fn right_hand_float(&mut self, key: &str, value: f64) -> &mut Self {
540        self.rhs.insert(
541            key.parse().unwrap(),
542            OneOrAll::One(ConditionValue::Float(value)),
543        );
544        self
545    }
546
547    /// Add a boolean value to the _right-hand-sidse_ of this condition.
548    pub fn right_hand_bool(&mut self, key: &str, value: bool) -> &mut Self {
549        self.rhs.insert(
550            key.parse().unwrap(),
551            OneOrAll::One(ConditionValue::Bool(value)),
552        );
553        self
554    }
555
556    /// Add the _if-exists_ constraint
557    pub fn if_exists(&mut self) -> &mut Self {
558        self.operator.if_exists = true;
559        self
560    }
561
562    ///
563    /// Convert this one condition into a complete Condition for a statement.
564    ///
565    pub fn build_as_condition(
566        &self,
567    ) -> HashMap<ConditionOperator, HashMap<QString, OneOrAll<ConditionValue>>> {
568        let mut map: HashMap<ConditionOperator, HashMap<QString, OneOrAll<ConditionValue>>> =
569            HashMap::default();
570        map.insert(self.operator.clone(), self.rhs.clone());
571        map
572    }
573}
574
575// ------------------------------------------------------------------------------------------------
576// Unit Tests
577// ------------------------------------------------------------------------------------------------
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582    use crate::io::write_to_writer;
583    use std::io::stdout;
584
585    #[test]
586    fn test_simple_builder() {
587        let policy: Policy = PolicyBuilder::new()
588            .named("confidential-data-access")
589            .evaluate_statement(
590                StatementBuilder::new()
591                    .auto_named()
592                    .allows()
593                    .unspecified_principals()
594                    .may_perform_actions(vec!["s3:List*", "s3:Get*"])
595                    .on_resources(vec![
596                        "arn:aws:s3:::confidential-data",
597                        "arn:aws:s3:::confidential-data/*",
598                    ])
599                    .if_condition(
600                        ConditionBuilder::new_bool()
601                            .right_hand_bool("aws:MultiFactorAuthPresent", true)
602                            .if_exists(),
603                    ),
604            )
605            .into();
606        write_to_writer(stdout(), &policy).expect("well that was unexpected");
607    }
608}