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}