iam_rs/evaluation/request.rs
1use super::context::Context;
2use crate::{Arn, Principal};
3use serde::{Deserialize, Serialize};
4
5/// Core IAM request containing principal, action, and resource
6///
7/// ## Understanding the PARC model
8///
9/// The PARC model represents the request context based on the four JSON elements in the policy language:
10///
11/// * Principal – The entity making the request.
12/// A principal represents a human user or programmatic workload that can be authenticated and
13/// then authorized to perform actions in AWS accounts.
14/// * Action – The operation being performed. Often the action will map to an API action.
15/// * Resource – The AWS resource on which the action is being performed.
16/// * Condition – Additional constraints that must be met for the request to be allowed.
17///
18/// The following shows an example of how the PARC model might represent a request context:
19///
20/// ```text
21/// Principal: AIDA123456789EXAMPLE
22/// Action: s3:CreateBucket
23/// Resource: arn:aws:s3:::amzn-s3-demo-bucket1
24/// Context:
25/// - aws:UserId=AIDA123456789EXAMPLE:BobsSession
26/// - aws:PrincipalAccount=123456789012
27/// - aws:PrincipalOrgId=o-example
28/// - aws:PrincipalARN=arn:aws:iam::AIDA123456789EXAMPLE:role/HR
29/// - aws:MultiFactorAuthPresent=true
30/// - aws:CurrentTime=...
31/// - aws:EpochTime=...
32/// - aws:SourceIp=...
33/// - aws:PrincipalTag/dept=123
34/// - aws:PrincipalTag/project=blue
35/// - aws:RequestTag/dept=123
36/// ```
37///
38/// <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-reqcontext.html>
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
41pub struct IAMRequest {
42 /// The principal making the request (e.g., AROA123456789EXAMPLE)
43 #[serde(rename = "Principal")]
44 pub principal: Principal,
45
46 /// The action being requested (e.g., iam:DeactivateMFADevice)
47 #[serde(rename = "Action")]
48 pub action: String,
49
50 /// The resource being accessed (e.g., `arn:aws:iam::user/martha`)
51 #[serde(rename = "Resource")]
52 pub resource: Arn,
53
54 /// Additional context for condition evaluation
55 #[serde(rename = "Context", default)]
56 pub context: Context,
57}
58
59impl IAMRequest {
60 /// Creates a new request
61 #[must_use]
62 pub fn new<S: Into<String>>(principal: Principal, action: S, resource: Arn) -> Self {
63 let action = action.into();
64 Self {
65 principal,
66 action,
67 resource,
68 context: Context::new(),
69 }
70 }
71
72 /// Creates a request with context
73 #[must_use]
74 pub fn new_with_context<S: Into<String>>(
75 principal: Principal,
76 action: S,
77 resource: Arn,
78 context: Context,
79 ) -> Self {
80 let action = action.into();
81 Self {
82 principal,
83 action,
84 resource,
85 context,
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use crate::PrincipalId;
93
94 use super::*;
95
96 #[test]
97 fn test_parc_request_creation() {
98 let request = IAMRequest::new(
99 Principal::Aws(PrincipalId::String("AROA123456789EXAMPLE".into())),
100 "iam:DeactivateMFADevice",
101 Arn::parse("arn:aws:iam:::user/martha").unwrap(),
102 );
103
104 assert_eq!(
105 request.principal,
106 Principal::Aws(PrincipalId::String("AROA123456789EXAMPLE".into()))
107 );
108 assert_eq!(request.action, "iam:DeactivateMFADevice");
109 assert_eq!(
110 request.resource,
111 Arn::parse("arn:aws:iam:::user/martha").unwrap()
112 );
113 }
114
115 #[test]
116 fn test_parc_request_with_context() {
117 let context = Context::new()
118 .with_string("aws:UserId", "AIDA123456789EXAMPLE:BobsSession")
119 .with_boolean("aws:MultiFactorAuthPresent", true)
120 .with_number("aws:EpochTime", 1633072800.0);
121 let request = IAMRequest::new_with_context(
122 Principal::Aws(PrincipalId::String("principal".into())),
123 "action",
124 Arn::parse("arn:aws:iam:::user/martha").unwrap(),
125 context,
126 );
127
128 assert_eq!(
129 request
130 .context
131 .get("aws:UserId")
132 .unwrap()
133 .as_string()
134 .unwrap(),
135 "AIDA123456789EXAMPLE:BobsSession"
136 );
137 assert_eq!(
138 request
139 .context
140 .get("aws:MultiFactorAuthPresent")
141 .unwrap()
142 .as_boolean()
143 .unwrap(),
144 true
145 );
146 assert_eq!(
147 request
148 .context
149 .get("aws:EpochTime")
150 .unwrap()
151 .as_number()
152 .unwrap(),
153 1633072800.0
154 );
155 }
156}