1use crate::{
2 decision::Effect,
3 error::{Error, Result},
4 request::Request,
5};
6
7#[derive(Debug, Clone)]
8pub struct Policy {
9 id: String,
10 target: Target,
11 conditions: Vec<Condition>,
12 effect: Effect,
13}
14
15impl Policy {
16 pub fn builder(id: impl Into<String>) -> PolicyBuilder {
17 PolicyBuilder {
18 id: id.into(),
19 target: None,
20 conditions: Vec::new(),
21 effect: None,
22 }
23 }
24
25 pub fn id(&self) -> &str {
26 &self.id
27 }
28
29 pub(crate) fn evaluate(&self, request: &Request) -> Result<Option<Effect>> {
30 if !self.target.matches(request) {
31 return Ok(None);
32 }
33
34 for condition in &self.conditions {
35 if !condition.is_satisfied(request)? {
36 return Ok(None);
37 }
38 }
39
40 Ok(Some(self.effect))
41 }
42}
43
44pub struct PolicyBuilder {
45 id: String,
46 target: Option<Target>,
47 conditions: Vec<Condition>,
48 effect: Option<Effect>,
49}
50
51impl PolicyBuilder {
52 pub fn target(mut self, target: Target) -> Self {
53 self.target = Some(target);
54 self
55 }
56
57 pub fn condition(mut self, condition: Condition) -> Self {
58 self.conditions.push(condition);
59 self
60 }
61
62 pub fn effect(mut self, effect: Effect) -> Self {
63 self.effect = Some(effect);
64 self
65 }
66
67 pub fn build(self) -> Result<Policy> {
68 let target = self.target.unwrap_or_else(Target::any);
69 let effect = self
70 .effect
71 .ok_or_else(|| Error::InvalidPolicy("effect must be specified".into()))?;
72
73 Ok(Policy {
74 id: self.id,
75 target,
76 conditions: self.conditions,
77 effect,
78 })
79 }
80}
81
82#[derive(Debug, Clone)]
83pub enum Target {
84 Any,
85 Action(String),
86}
87
88impl Target {
89 pub fn any() -> Self {
90 Target::Any
91 }
92
93 pub fn action(action: impl Into<String>) -> Self {
94 Target::Action(action.into())
95 }
96
97 pub(crate) fn matches(&self, request: &Request) -> bool {
98 match self {
99 Target::Any => true,
100 Target::Action(expected) => request.action_name() == expected,
101 }
102 }
103}
104
105#[derive(Debug, Clone)]
106pub enum Condition {
107 Equals(String, String),
108}
109
110impl Condition {
111 pub fn equals(left: impl Into<String>, right: impl Into<String>) -> Self {
112 Condition::Equals(left.into(), right.into())
113 }
114
115 pub(crate) fn is_satisfied(&self, request: &Request) -> Result<bool> {
116 match self {
117 Condition::Equals(lhs, rhs) => {
118 let left = resolve_operand(request, lhs)?;
119 let right = resolve_operand(request, rhs)?;
120 Ok(left == right)
121 }
122 }
123 }
124}
125
126fn resolve_operand(request: &Request, operand: &str) -> Result<String> {
127 if let Some((namespace, key)) = parse_path(operand) {
128 request
129 .lookup(namespace, key.as_deref())
130 .map(str::to_owned)
131 .ok_or_else(|| Error::MissingAttribute(format!("{namespace}{}", key_to_suffix(&key))))
132 } else {
133 Ok(operand.to_owned())
134 }
135}
136
137fn parse_path(raw: &str) -> Option<(&str, Option<String>)> {
138 if let Some((namespace, key)) = raw.split_once('.') {
139 Some((namespace, Some(key.to_string())))
140 } else {
141 match raw {
142 "actor" | "resource" | "environment" | "action" => {
143 Some((raw, None))
144 }
145 _ => None,
146 }
147 }
148}
149
150fn key_to_suffix(key: &Option<String>) -> String {
151 key.as_ref()
152 .map(|k| format!(".{k}"))
153 .unwrap_or_default()
154}