iam_rs/evaluation/
request.rs

1use super::context::{Context, ContextValue};
2use serde::{Deserialize, Serialize};
3
4/// Core IAM request containing principal, action, and resource
5///
6/// ## Understanding the PARC model
7///
8/// The PARC model represents the request context based on the four JSON elements in the policy language:
9///
10/// * Principal – The entity making the request.
11///         A principal represents a human user or programmatic workload that can be authenticated and
12///         then authorized to perform actions in AWS accounts.
13/// * Action – The operation being performed. Often the action will map to an API action.
14/// * Resource – The AWS resource on which the action is being performed.
15/// * Condition – Additional constraints that must be met for the request to be allowed.
16///
17/// The following shows an example of how the PARC model might represent a request context:
18///
19/// ```text
20/// Principal: AIDA123456789EXAMPLE
21/// Action: s3:CreateBucket
22/// Resource: arn:aws:s3:::amzn-s3-demo-bucket1
23/// Context:
24/// - aws:UserId=AIDA123456789EXAMPLE:BobsSession
25/// - aws:PrincipalAccount=123456789012
26/// - aws:PrincipalOrgId=o-example
27/// - aws:PrincipalARN=arn:aws:iam::AIDA123456789EXAMPLE:role/HR
28/// - aws:MultiFactorAuthPresent=true
29/// - aws:CurrentTime=...
30/// - aws:EpochTime=...
31/// - aws:SourceIp=...
32/// - aws:PrincipalTag/dept=123
33/// - aws:PrincipalTag/project=blue
34/// - aws:RequestTag/dept=123
35/// ```
36///
37/// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-reqcontext.html
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct IAMRequest {
40    /// The principal making the request (e.g., AROA123456789EXAMPLE)
41    pub principal: String,
42
43    /// The action being requested (e.g., iam:DeactivateMFADevice)
44    pub action: String,
45
46    /// The resource being accessed (e.g., arn:aws:iam::user/martha)
47    pub resource: String,
48
49    /// Additional context for condition evaluation
50    pub context: Context,
51}
52
53impl IAMRequest {
54    /// Creates a new request
55    pub fn new<S: Into<String>>(principal: S, action: S, resource: S) -> Self {
56        Self {
57            principal: principal.into(),
58            action: action.into(),
59            resource: resource.into(),
60            context: Context::new(),
61        }
62    }
63
64    /// Creates a request with context
65    pub fn new_with_context<S: Into<String>>(
66        principal: S,
67        action: S,
68        resource: S,
69        context: Context,
70    ) -> Self {
71        Self {
72            principal: principal.into(),
73            action: action.into(),
74            resource: resource.into(),
75            context,
76        }
77    }
78
79    /// Adds all context key-value pairs from another context
80    pub fn with_context(mut self, other_context: Context) -> Self {
81        self.context.extend(other_context);
82        self
83    }
84
85    /// Adds string context to the request
86    pub fn with_string_context<K: Into<String>, V: Into<String>>(
87        mut self,
88        key: K,
89        value: V,
90    ) -> Self {
91        self.context = self.context.with_string(key, value);
92        self
93    }
94
95    /// Adds boolean context to the request
96    pub fn with_boolean_context<K: Into<String>>(mut self, key: K, value: bool) -> Self {
97        self.context = self.context.with_boolean(key, value);
98        self
99    }
100
101    /// Adds numeric context to the request
102    pub fn with_number_context<K: Into<String>>(mut self, key: K, value: f64) -> Self {
103        self.context = self.context.with_number(key, value);
104        self
105    }
106
107    /// Gets a context value by key
108    pub fn get_context(&self, key: &str) -> Option<&ContextValue> {
109        self.context.get(key)
110    }
111
112    /// Checks if a context key exists
113    pub fn has_context(&self, key: &str) -> bool {
114        self.context.has_key(key)
115    }
116
117    /// Gets all context keys
118    pub fn context_keys(&self) -> Vec<&String> {
119        self.context.keys()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_parc_request_creation() {
129        let request = IAMRequest::new(
130            "AROA123456789EXAMPLE",
131            "iam:DeactivateMFADevice",
132            "arn:aws:iam::user/martha",
133        );
134
135        assert_eq!(request.principal, "AROA123456789EXAMPLE");
136        assert_eq!(request.action, "iam:DeactivateMFADevice");
137        assert_eq!(request.resource, "arn:aws:iam::user/martha");
138    }
139
140    #[test]
141    fn test_parc_request_with_context() {
142        let request = IAMRequest::new("principal", "action", "resource")
143            .with_string_context("string_key", "string_value")
144            .with_boolean_context("bool_key", true)
145            .with_number_context("number_key", 42.0);
146
147        assert_eq!(
148            request
149                .get_context("string_key")
150                .unwrap()
151                .as_string()
152                .unwrap(),
153            "string_value"
154        );
155        assert_eq!(
156            request
157                .get_context("bool_key")
158                .unwrap()
159                .as_boolean()
160                .unwrap(),
161            true
162        );
163        assert_eq!(
164            request
165                .get_context("number_key")
166                .unwrap()
167                .as_number()
168                .unwrap(),
169            42.0
170        );
171    }
172
173    #[test]
174    fn test_parc_request_context_utilities() {
175        let request = IAMRequest::new("principal", "action", "resource")
176            .with_string_context("key1", "value1")
177            .with_boolean_context("key2", false);
178
179        assert!(request.has_context("key1"));
180        assert!(request.has_context("key2"));
181        assert!(!request.has_context("key3"));
182
183        let keys = request.context_keys();
184        assert_eq!(keys.len(), 2);
185        assert!(keys.contains(&&"key1".to_string()));
186        assert!(keys.contains(&&"key2".to_string()));
187    }
188}