1use crate::model::*;
36use std::collections::HashMap;
37
38#[derive(Debug, Default)]
46pub struct PolicyBuilder {
47 version: Option<Version>,
48 id: Option<String>,
49 statements: Vec<Statement>,
50}
51
52#[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#[derive(Debug)]
71pub struct ConditionBuilder {
72 operator: ConditionOperator,
73 rhs: HashMap<QString, OneOrAll<ConditionValue>>,
74}
75
76impl PolicyBuilder {
81 pub fn new() -> Self {
83 Default::default()
84 }
85
86 pub fn version(&mut self, version: Version) -> &mut Self {
88 self.version = Some(version);
89 self
90 }
91
92 pub fn default_version(&mut self) -> &mut Self {
94 self.version = Some(Policy::default_version());
95 self
96 }
97
98 pub fn named(&mut self, id: &str) -> &mut Self {
100 self.id = Some(id.to_string());
101 self
102 }
103
104 pub fn auto_named(&mut self) -> &mut Self {
106 self.id = Some(Policy::new_id());
107 self
108 }
109
110 pub fn evaluate_statement(&mut self, statement: &mut StatementBuilder) -> &mut Self {
112 self.statements.push(statement.into());
113 self
114 }
115
116 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 pub fn new() -> Self {
160 Default::default()
161 }
162
163 pub fn named(&mut self, sid: &str) -> &mut Self {
165 self.sid = Some(sid.to_string());
166 self
167 }
168
169 pub fn auto_named(&mut self) -> &mut Self {
171 self.sid = Some(Statement::new_sid());
172 self
173 }
174
175 pub fn allows(&mut self) -> &mut Self {
177 self.effect = Effect::Allow;
178 self
179 }
180
181 pub fn does_not_allow(&mut self) -> &mut Self {
183 self.effect = Effect::Deny;
184 self
185 }
186
187 pub fn unspecified_principals(&mut self) -> &mut Self {
189 self.principals.clear();
190 self
191 }
192
193 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 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 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 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 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 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 pub fn may_perform_action(&mut self, action: &str) -> &mut Self {
245 self.may_perform_actions(vec![action]);
246 self
247 }
248
249 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 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 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 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 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 pub fn on_resource(&mut self, resource: &str) -> &mut Self {
303 self.on_resources(vec![resource]);
304 self
305 }
306
307 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 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 pub fn not_on_resource(&mut self, resource: &str) -> &mut Self {
332 self.not_on_resources(vec![resource]);
333 self
334 }
335
336 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 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 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 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 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 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 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 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 pub fn for_all(&mut self) -> &mut Self {
499 self.operator.quantifier = Some(ConditionOperatorQuantifier::ForAllValues);
500 self
501 }
502
503 pub fn for_any(&mut self) -> &mut Self {
505 self.operator.quantifier = Some(ConditionOperatorQuantifier::ForAnyValue);
506 self
507 }
508
509 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 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 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 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 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 pub fn if_exists(&mut self) -> &mut Self {
558 self.operator.if_exists = true;
559 self
560 }
561
562 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#[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}