scratchstack_aws_principal/
federated_user.rs1use {
2 crate::{utils::validate_name, PrincipalError},
3 scratchstack_arn::{
4 utils::{validate_account_id, validate_partition},
5 Arn,
6 },
7 std::fmt::{Display, Formatter, Result as FmtResult},
8};
9
10#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct FederatedUser {
15 partition: String,
17
18 account_id: String,
20
21 user_name: String,
23}
24
25impl FederatedUser {
26 pub fn new(partition: &str, account_id: &str, user_name: &str) -> Result<Self, PrincipalError> {
49 validate_partition(partition)?;
50 validate_account_id(account_id)?;
51 validate_name(user_name, 32, PrincipalError::InvalidFederatedUserName)?;
52
53 if user_name.len() < 2 {
54 Err(PrincipalError::InvalidFederatedUserName(user_name.into()))
55 } else {
56 Ok(Self {
57 partition: partition.into(),
58 account_id: account_id.into(),
59 user_name: user_name.into(),
60 })
61 }
62 }
63
64 #[inline]
66 pub fn partition(&self) -> &str {
67 &self.partition
68 }
69
70 #[inline]
72 pub fn account_id(&self) -> &str {
73 &self.account_id
74 }
75
76 #[inline]
78 pub fn user_name(&self) -> &str {
79 &self.user_name
80 }
81}
82
83impl From<&FederatedUser> for Arn {
84 fn from(user: &FederatedUser) -> Arn {
85 Arn::new(&user.partition, "sts", "", &user.account_id, &format!("federated-user/{}", user.user_name)).unwrap()
86 }
87}
88
89impl Display for FederatedUser {
90 fn fmt(&self, f: &mut Formatter) -> FmtResult {
91 write!(f, "arn:{}:sts::{}:federated-user/{}", self.partition, self.account_id, self.user_name)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use {
98 super::FederatedUser,
99 crate::{PrincipalIdentity, PrincipalSource},
100 scratchstack_arn::Arn,
101 std::{
102 collections::hash_map::DefaultHasher,
103 hash::{Hash, Hasher},
104 },
105 };
106
107 #[test]
108 fn check_components() {
109 let user = FederatedUser::new("aws", "123456789012", "test-user").unwrap();
110 assert_eq!(user.partition(), "aws");
111 assert_eq!(user.account_id(), "123456789012");
112 assert_eq!(user.user_name(), "test-user");
113
114 let arn: Arn = (&user).into();
115 assert_eq!(arn.partition(), "aws");
116 assert_eq!(arn.service(), "sts");
117 assert_eq!(arn.account_id(), "123456789012");
118 assert_eq!(arn.resource(), "federated-user/test-user");
119
120 let p = PrincipalIdentity::from(user);
121 let source = p.source();
122 assert_eq!(source, PrincipalSource::Federated);
123 assert_eq!(source.to_string(), "Federated");
124 }
125
126 #[test]
127 #[allow(clippy::redundant_clone)]
128 fn check_derived() {
129 let u1a = FederatedUser::new("aws", "123456789012", "test-user1").unwrap();
130 let u1b = FederatedUser::new("aws", "123456789012", "test-user1").unwrap();
131 let u2 = FederatedUser::new("aws", "123456789012", "test-user2").unwrap();
132 let u3 = FederatedUser::new("aws", "123456789013", "test-user2").unwrap();
133 let u4 = FederatedUser::new("awt", "123456789013", "test-user2").unwrap();
134
135 assert_eq!(u1a, u1b);
136 assert_ne!(u1a, u2);
137 assert_eq!(u1a.clone(), u1a);
138
139 let mut h1a = DefaultHasher::new();
141 let mut h1b = DefaultHasher::new();
142 let mut h2 = DefaultHasher::new();
143 u1a.hash(&mut h1a);
144 u1b.hash(&mut h1b);
145 u2.hash(&mut h2);
146 let hash1a = h1a.finish();
147 let hash1b = h1b.finish();
148 let hash2 = h2.finish();
149 assert_eq!(hash1a, hash1b);
150 assert_ne!(hash1a, hash2);
151
152 assert!(u1a <= u1b);
154 assert!(u1a < u2);
155 assert!(u2 > u1a);
156 assert!(u2 < u3);
157 assert!(u3 > u2);
158 assert!(u1a < u3);
159 assert!(u3 < u4);
160
161 assert_eq!(u1a.clone().max(u2.clone()), u2);
162 assert_eq!(u1a.clone().min(u2), u1a);
163
164 assert_eq!(u1a.to_string(), "arn:aws:sts::123456789012:federated-user/test-user1");
166
167 let _ = format!("{u1a:?}");
169 }
170
171 #[test]
172 fn check_valid_federated_users() {
173 let f1a = FederatedUser::new("aws", "123456789012", "user@domain").unwrap();
174 let f1b = FederatedUser::new("aws", "123456789012", "user@domain").unwrap();
175 let f2 = FederatedUser::new("partition-with-32-characters1234", "123456789012", "user@domain").unwrap();
176 let f3 = FederatedUser::new("aws", "123456789012", "user@domain-with_32-characters==").unwrap();
177
178 assert_eq!(f1a, f1b);
179 assert_ne!(f1a, f2);
180 assert_eq!(f1a, f1a.clone());
181
182 assert_eq!(f1a.to_string(), "arn:aws:sts::123456789012:federated-user/user@domain");
183 assert_eq!(f2.to_string(), "arn:partition-with-32-characters1234:sts::123456789012:federated-user/user@domain");
184 assert_eq!(f3.to_string(), "arn:aws:sts::123456789012:federated-user/user@domain-with_32-characters==");
185 }
186
187 #[test]
188 fn check_invalid_federated_users() {
189 assert_eq!(
190 FederatedUser::new("", "123456789012", "user@domain",).unwrap_err().to_string(),
191 r#"Invalid partition: """#
192 );
193
194 assert_eq!(FederatedUser::new("aws", "", "user@domain",).unwrap_err().to_string(), r#"Invalid account id: """#);
195
196 assert_eq!(
197 FederatedUser::new("aws", "123456789012", "",).unwrap_err().to_string(),
198 r#"Invalid federated user name: """#
199 );
200
201 assert_eq!(
202 FederatedUser::new("aws", "123456789012", "user!name@domain",).unwrap_err().to_string(),
203 r#"Invalid federated user name: "user!name@domain""#
204 );
205
206 assert_eq!(
207 FederatedUser::new("aws", "123456789012", "u",).unwrap_err().to_string(),
208 r#"Invalid federated user name: "u""#
209 );
210
211 assert_eq!(
212 FederatedUser::new("partition-with-33-characters12345", "123456789012", "user@domain",)
213 .unwrap_err()
214 .to_string(),
215 r#"Invalid partition: "partition-with-33-characters12345""#
216 );
217
218 assert_eq!(
219 FederatedUser::new("aws", "1234567890123", "user@domain",).unwrap_err().to_string(),
220 r#"Invalid account id: "1234567890123""#
221 );
222
223 assert_eq!(
224 FederatedUser::new("aws", "123456789012", "user@domain-with-33-characters===",).unwrap_err().to_string(),
225 r#"Invalid federated user name: "user@domain-with-33-characters===""#
226 );
227 }
228}
229