scratchstack_aspen/principal/
specified.rs1use {
2 super::AwsPrincipal,
3 crate::{display_json, from_str_json, serutil::StringLikeList},
4 derive_builder::Builder,
5 scratchstack_aws_principal::{Principal as PrincipalActor, PrincipalIdentity, PrincipalSource},
6 serde::{Deserialize, Serialize},
7};
8
9#[derive(Builder, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
13pub struct SpecifiedPrincipal {
14 #[builder(setter(into, strip_option), default)]
16 #[serde(rename = "AWS", skip_serializing_if = "Option::is_none")]
17 aws: Option<StringLikeList<AwsPrincipal>>,
18
19 #[builder(setter(into, strip_option), default)]
21 #[serde(rename = "CanonicalUser", skip_serializing_if = "Option::is_none")]
22 canonical_user: Option<StringLikeList<String>>,
23
24 #[builder(setter(into, strip_option), default)]
26 #[serde(rename = "Federated", skip_serializing_if = "Option::is_none")]
27 federated: Option<StringLikeList<String>>,
28
29 #[builder(setter(into, strip_option), default)]
31 #[serde(rename = "Service", skip_serializing_if = "Option::is_none")]
32 service: Option<StringLikeList<String>>,
33}
34
35display_json!(SpecifiedPrincipal);
36from_str_json!(SpecifiedPrincipal);
37
38impl SpecifiedPrincipal {
39 #[inline]
41 pub fn builder() -> SpecifiedPrincipalBuilder {
42 SpecifiedPrincipalBuilder::default()
43 }
44
45 #[inline]
47 pub fn aws(&self) -> Option<&StringLikeList<AwsPrincipal>> {
48 self.aws.as_ref()
49 }
50
51 #[inline]
53 pub fn canonical_user(&self) -> Option<&StringLikeList<String>> {
54 self.canonical_user.as_ref()
55 }
56
57 #[inline]
59 pub fn federated(&self) -> Option<&StringLikeList<String>> {
60 self.federated.as_ref()
61 }
62
63 #[inline]
65 pub fn service(&self) -> Option<&StringLikeList<String>> {
66 self.service.as_ref()
67 }
68
69 pub fn matches(&self, actor: &PrincipalActor) -> bool {
71 for identity in actor.iter() {
72 let source = identity.source();
73 match source {
74 PrincipalSource::Aws => {
75 if let Some(aws_ids) = self.aws() {
76 for aws_id in aws_ids.iter() {
77 if aws_id.matches(identity) {
78 return true;
79 }
80 }
81 }
82 }
83 PrincipalSource::CanonicalUser => {
84 if let PrincipalIdentity::CanonicalUser(identity) = identity {
85 if let Some(canonical_users) = self.canonical_user() {
86 for canonical_user in canonical_users.iter() {
87 if canonical_user == identity.canonical_user_id() {
88 return true;
89 }
90 }
91 }
92 }
93 }
94 PrincipalSource::Federated => {
95 if let PrincipalIdentity::FederatedUser(identity) = identity {
96 if let Some(federated) = self.federated() {
97 for federated in federated.iter() {
98 if federated == identity.user_name() {
99 return true;
100 }
101 }
102 }
103 }
104 }
105 PrincipalSource::Service => {
106 if let PrincipalIdentity::Service(identity) = identity {
107 if let Some(services) = self.service() {
108 for service in services.iter() {
109 if service == identity.global_dns_name().as_str()
110 || service == identity.regional_dns_name().as_str()
111 {
112 return true;
113 }
114 }
115 }
116 }
117 }
118 }
119 }
120
121 false
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use {
128 super::SpecifiedPrincipal,
129 scratchstack_aws_principal::{
130 CanonicalUser, FederatedUser, Principal as PrincipalActor, PrincipalIdentity, Service, User,
131 },
132 std::str::FromStr,
133 };
134
135 #[test_log::test]
136 fn test_deserialize_basic1() {
137 let sp = SpecifiedPrincipal::from_str(
138 r#"
139 {
140 "AWS": ["123456789012", "arn:aws:iam::123456789012:user/dacut"],
141 "CanonicalUser": ["df22d4799ef444d6434c676951d8b390145f2fc5f9107140d0e4b733ad40516d"],
142 "Federated": ["dacut@kanga.org"],
143 "Service": ["ec2.amazonaws.com", "lambda.amazonaws.com"]
144 }
145 "#,
146 )
147 .unwrap();
148
149 assert!(sp.aws.is_some());
150 assert!(sp.canonical_user.is_some());
151 assert!(sp.federated.is_some());
152 assert!(sp.service.is_some());
153 }
154
155 #[test_log::test]
156 fn test_matches() {
157 let sp = SpecifiedPrincipal::from_str(
158 r#"
159 {
160 "AWS": ["arn:aws:iam::123456789012:user/dacut"],
161 "CanonicalUser": ["df22d4799ef444d6434c676951d8b390145f2fc5f9107140d0e4b733ad40516d"],
162 "Federated": ["dacut@kanga.org"],
163 "Service": ["ec2.amazonaws.com", "lambda.amazonaws.com"]
164 }
165 "#,
166 )
167 .unwrap();
168
169 let user = User::new("aws", "123456789012", "/", "dacut").unwrap();
170 let canonical_user =
171 CanonicalUser::new("df22d4799ef444d6434c676951d8b390145f2fc5f9107140d0e4b733ad40516d").unwrap();
172 let federated_user = FederatedUser::new("aws", "123456789012", "dacut@kanga.org").unwrap();
173 let lambda_regional = Service::new("lambda", Some("us-east-1".to_string()), "amazonaws.com").unwrap();
174
175 assert!(sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(user.clone())])));
176 assert!(sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(canonical_user.clone())])));
177 assert!(sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(federated_user.clone())])));
178 assert!(sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(lambda_regional.clone())])));
179
180 let user_wrong_partition = User::new("aws-us-gov", "123456789012", "/", "dacut").unwrap();
181 assert!(!sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(user_wrong_partition)])));
182
183 let wrong_canonical_user =
184 CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
185 assert!(!sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(wrong_canonical_user)])));
186
187 let wrong_federated_user = FederatedUser::new("aws", "123456789012", "evildoer@kanga.org").unwrap();
188 assert!(!sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(wrong_federated_user)])));
189
190 let wrong_service = Service::new("s3", None, "amazonaws.com").unwrap();
191 assert!(!sp.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(wrong_service)])));
192
193 let empty = SpecifiedPrincipal::builder().build().unwrap();
194 assert!(!empty.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(user)])));
195 assert!(!empty.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(canonical_user)])));
196 assert!(!empty.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(federated_user)])));
197 assert!(!empty.matches(&PrincipalActor::from(vec![PrincipalIdentity::from(lambda_regional)])));
198 }
199}