scratchstack_aws_principal/
principal.rs

1use {
2    crate::{AssumedRole, CanonicalUser, FederatedUser, PrincipalError, RootUser, Service, User},
3    scratchstack_arn::Arn,
4    std::{
5        default::Default,
6        fmt::{Debug, Display, Formatter, Result as FmtResult},
7        hash::Hash,
8        iter::IntoIterator,
9        ops::Deref,
10        str::FromStr,
11    },
12};
13
14/// The source of a principal.
15#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
16pub enum PrincipalSource {
17    /// AWS account or IAM principal
18    Aws,
19
20    /// S3 canonical user
21    CanonicalUser,
22
23    /// Federated identity
24    Federated,
25
26    /// Service principal
27    Service,
28}
29
30impl Display for PrincipalSource {
31    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
32        match self {
33            Self::Aws => f.write_str("AWS"),
34            Self::CanonicalUser => f.write_str("CanonicalUser"),
35            Self::Federated => f.write_str("Federated"),
36            Self::Service => f.write_str("Service"),
37        }
38    }
39}
40
41/// A principal that is the source of an action in an AWS (or AWS-like) service.
42///
43/// This principal may have multiple aspects to its identity: for example, an assumed role may have an associated
44/// service and S3 canonical user. Thus, the principal is represented as a set of [PrincipalIdentity] values.
45#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
46pub struct Principal {
47    /// A sorted vector of identities.
48    identities: Vec<PrincipalIdentity>,
49}
50
51impl Principal {
52    /// Create a new principal from a vector of identities.
53    pub fn new(mut identities: Vec<PrincipalIdentity>) -> Self {
54        identities.sort_unstable();
55        identities.dedup();
56        Self {
57            identities,
58        }
59    }
60
61    /// Create an empty principal with the specified capacity.
62    pub fn with_capacity(capacity: usize) -> Self {
63        Self {
64            identities: Vec::with_capacity(capacity),
65        }
66    }
67
68    /// Adds an identity to the principal.
69    pub fn add(&mut self, identity: PrincipalIdentity) {
70        self.identities.push(identity);
71        self.identities.sort_unstable();
72        self.identities.dedup();
73    }
74
75    /// Extracts a slice containing the identities.
76    pub fn as_slice(&self) -> &[PrincipalIdentity] {
77        &self.identities
78    }
79
80    /// Clears this principal, removing all identities.
81    pub fn clear(&mut self) {
82        self.identities.clear();
83    }
84
85    /// Returns `true` if the principal has no identities.
86    pub fn is_empty(&self) -> bool {
87        self.identities.is_empty()
88    }
89
90    /// Returns the number of identities in the principal.
91    pub fn len(&self) -> usize {
92        self.identities.len()
93    }
94
95    /// Removes and returns the identity at position `index`.
96    ///
97    /// # Panics
98    /// Panics if `index` is out of bounds.
99    pub fn remove(&mut self, index: usize) -> PrincipalIdentity {
100        self.identities.remove(index)
101    }
102
103    /// Keep the first `len` identities and drop the rest. If `len` is greater than the current number of identities,
104    /// this has no effect.
105    pub fn truncate(&mut self, len: usize) {
106        self.identities.truncate(len)
107    }
108}
109
110impl AsRef<[PrincipalIdentity]> for Principal {
111    fn as_ref(&self) -> &[PrincipalIdentity] {
112        self.identities.as_ref()
113    }
114}
115
116impl Deref for Principal {
117    type Target = [PrincipalIdentity];
118
119    fn deref(&self) -> &[PrincipalIdentity] {
120        self.identities.deref()
121    }
122}
123
124impl Display for Principal {
125    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
126        f.write_str("Principal(")?;
127        let mut first = true;
128        for identity in &self.identities {
129            if first {
130                first = false;
131            } else {
132                f.write_str(", ")?;
133            }
134            Display::fmt(identity, f)?;
135        }
136        f.write_str(")")
137    }
138}
139
140impl From<&[PrincipalIdentity]> for Principal {
141    fn from(identities: &[PrincipalIdentity]) -> Self {
142        Self::new(identities.to_vec())
143    }
144}
145
146impl From<Vec<PrincipalIdentity>> for Principal {
147    fn from(identities: Vec<PrincipalIdentity>) -> Self {
148        Self::new(identities)
149    }
150}
151
152impl From<&Vec<PrincipalIdentity>> for Principal {
153    fn from(identities: &Vec<PrincipalIdentity>) -> Self {
154        Self::new(identities.clone())
155    }
156}
157
158impl From<AssumedRole> for Principal {
159    fn from(role: AssumedRole) -> Self {
160        Self::new(vec![PrincipalIdentity::AssumedRole(role)])
161    }
162}
163
164impl From<CanonicalUser> for Principal {
165    fn from(user: CanonicalUser) -> Self {
166        Self::new(vec![PrincipalIdentity::CanonicalUser(user)])
167    }
168}
169
170impl From<FederatedUser> for Principal {
171    fn from(user: FederatedUser) -> Self {
172        Self::new(vec![PrincipalIdentity::FederatedUser(user)])
173    }
174}
175
176impl From<RootUser> for Principal {
177    fn from(user: RootUser) -> Self {
178        Self::new(vec![PrincipalIdentity::RootUser(user)])
179    }
180}
181
182impl From<Service> for Principal {
183    fn from(service: Service) -> Self {
184        Self::new(vec![PrincipalIdentity::Service(service)])
185    }
186}
187
188impl From<User> for Principal {
189    fn from(user: User) -> Self {
190        Self::new(vec![PrincipalIdentity::User(user)])
191    }
192}
193
194impl<'a> IntoIterator for &'a Principal {
195    type Item = &'a PrincipalIdentity;
196    type IntoIter = std::slice::Iter<'a, PrincipalIdentity>;
197
198    fn into_iter(self) -> Self::IntoIter {
199        self.identities.iter()
200    }
201}
202
203impl IntoIterator for Principal {
204    type Item = PrincipalIdentity;
205    type IntoIter = std::vec::IntoIter<PrincipalIdentity>;
206
207    fn into_iter(self) -> Self::IntoIter {
208        self.identities.into_iter()
209    }
210}
211
212/// A principal identity that is the source of an action in an AWS (or AWS-like) service.
213///
214/// `From` conversions are provided for each specific type of identity.
215///
216/// # Examples
217///
218/// ```
219/// # use scratchstack_aws_principal::{PrincipalIdentity, User};
220/// # use std::str::FromStr;
221/// let pi: PrincipalIdentity = User::from_str("arn:aws:iam::123456789012:user/username").unwrap().into();
222/// assert_eq!(pi.as_user().unwrap().user_name(), "username");
223/// ```
224#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
225pub enum PrincipalIdentity {
226    /// Details for an assumed role.
227    AssumedRole(AssumedRole),
228
229    /// Details for an S3 canonical user.
230    CanonicalUser(CanonicalUser),
231
232    /// Details for a federated user.
233    FederatedUser(FederatedUser),
234
235    /// Details for the root user of an account.
236    RootUser(RootUser),
237
238    /// Details for a service.
239    Service(Service),
240
241    /// Details for an IAM user.
242    User(User),
243}
244
245impl PrincipalIdentity {
246    /// Return the source of this principal.
247    pub fn source(&self) -> PrincipalSource {
248        match self {
249            Self::AssumedRole(_) | Self::RootUser(_) | Self::User(_) => PrincipalSource::Aws,
250            Self::CanonicalUser(_) => PrincipalSource::CanonicalUser,
251            Self::FederatedUser(_) => PrincipalSource::Federated,
252            Self::Service(_) => PrincipalSource::Service,
253        }
254    }
255
256    /// Indicates whether this principal has an associated ARN.
257    ///
258    /// To obtain the ARN, use code similar to the following:
259    /// ```
260    /// # use scratchstack_aws_principal::{PrincipalIdentity, User};
261    /// # use scratchstack_arn::Arn;
262    /// let ident = PrincipalIdentity::User(User::new("aws", "123456789012", "/", "username").unwrap());
263    /// if ident.has_arn() {
264    ///     let arn: Arn = ident.try_into().unwrap();
265    /// }
266    /// ```
267    pub fn has_arn(&self) -> bool {
268        !matches!(self, Self::CanonicalUser(_) | Self::Service(_))
269    }
270
271    /// If the principal identity is an assumed role, return it. Otherwise, return `None`.
272    #[inline]
273    pub fn as_assumed_role(&self) -> Option<&AssumedRole> {
274        match self {
275            Self::AssumedRole(role) => Some(role),
276            _ => None,
277        }
278    }
279
280    /// If the principal identity is a canonical user, return it. Otherwise, return `None`.
281    #[inline]
282    pub fn as_canonical_user(&self) -> Option<&CanonicalUser> {
283        match self {
284            Self::CanonicalUser(user) => Some(user),
285            _ => None,
286        }
287    }
288
289    /// If the principal identity is a federated user, return it. Otherwise, return `None`.
290    #[inline]
291    pub fn as_federated_user(&self) -> Option<&FederatedUser> {
292        match self {
293            Self::FederatedUser(user) => Some(user),
294            _ => None,
295        }
296    }
297
298    /// If the principal identity is a root user, return it. Otherwise, return `None`.
299    #[inline]
300    pub fn as_root_user(&self) -> Option<&RootUser> {
301        match self {
302            Self::RootUser(user) => Some(user),
303            _ => None,
304        }
305    }
306
307    /// If the principal identity is a service, return it. Otherwise, return `None`.
308    #[inline]
309    pub fn as_service(&self) -> Option<&Service> {
310        match self {
311            Self::Service(service) => Some(service),
312            _ => None,
313        }
314    }
315
316    /// If the principal identity is a user, return it. Otherwise, return `None`.
317    #[inline]
318    pub fn as_user(&self) -> Option<&User> {
319        match self {
320            Self::User(user) => Some(user),
321            _ => None,
322        }
323    }
324
325    /// Parse an ARN, possibly returning a principal identity. This is mainly a convenience function for unit tests.
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// # use scratchstack_aws_principal::PrincipalIdentity;
331    /// let pi = PrincipalIdentity::parse_arn("arn:aws:iam::123456789012:user/username").unwrap();
332    /// assert!(pi.as_user().is_some());
333    /// ```
334    pub fn parse_arn(arn: &str) -> Result<Self, PrincipalError> {
335        let parsed_arn = Arn::from_str(arn)?;
336        let service = parsed_arn.service();
337        let resource = parsed_arn.resource();
338
339        match service {
340            "sts" if resource.starts_with("assumed-role/") => {
341                return Ok(AssumedRole::try_from(&parsed_arn)?.into());
342            }
343            "iam" => {
344                if resource.starts_with("user/") {
345                    return Ok(User::try_from(&parsed_arn)?.into());
346                }
347            }
348            _ => {}
349        }
350
351        Err(PrincipalError::InvalidArn(arn.to_string()))
352    }
353}
354
355/// Wrap an [AssumedRole] in a [PrincipalIdentity].
356impl From<AssumedRole> for PrincipalIdentity {
357    fn from(assumed_role: AssumedRole) -> Self {
358        PrincipalIdentity::AssumedRole(assumed_role)
359    }
360}
361
362/// Wrap a [CanonicalUser] in a [PrincipalIdentity].
363impl From<CanonicalUser> for PrincipalIdentity {
364    fn from(canonical_user: CanonicalUser) -> Self {
365        PrincipalIdentity::CanonicalUser(canonical_user)
366    }
367}
368
369/// Wrap a [FederatedUser] in a [PrincipalIdentity].
370impl From<FederatedUser> for PrincipalIdentity {
371    fn from(federated_user: FederatedUser) -> Self {
372        PrincipalIdentity::FederatedUser(federated_user)
373    }
374}
375
376/// Wrap a [RootUser] in a [PrincipalIdentity].
377impl From<RootUser> for PrincipalIdentity {
378    fn from(root_user: RootUser) -> Self {
379        PrincipalIdentity::RootUser(root_user)
380    }
381}
382
383/// Wrap a [Service] in a [PrincipalIdentity].
384impl From<Service> for PrincipalIdentity {
385    fn from(service: Service) -> Self {
386        PrincipalIdentity::Service(service)
387    }
388}
389
390/// Wrap a [User] in a [PrincipalIdentity].
391impl From<User> for PrincipalIdentity {
392    fn from(user: User) -> Self {
393        PrincipalIdentity::User(user)
394    }
395}
396
397impl Debug for PrincipalIdentity {
398    fn fmt(&self, f: &mut Formatter) -> FmtResult {
399        match self {
400            PrincipalIdentity::AssumedRole(assumed_role) => f.debug_tuple("AssumedRole").field(assumed_role).finish(),
401            PrincipalIdentity::CanonicalUser(canonical_user) => {
402                f.debug_tuple("CanonicalUser").field(canonical_user).finish()
403            }
404            PrincipalIdentity::FederatedUser(federated_user) => {
405                f.debug_tuple("FederatedUser").field(federated_user).finish()
406            }
407            PrincipalIdentity::RootUser(root_user) => f.debug_tuple("RootUser").field(root_user).finish(),
408            PrincipalIdentity::Service(service) => f.debug_tuple("Service").field(service).finish(),
409            PrincipalIdentity::User(user) => f.debug_tuple("User").field(user).finish(),
410        }
411    }
412}
413
414impl Display for PrincipalIdentity {
415    fn fmt(&self, f: &mut Formatter) -> FmtResult {
416        match self {
417            Self::AssumedRole(ref inner) => Display::fmt(inner, f),
418            Self::CanonicalUser(ref inner) => Display::fmt(inner, f),
419            Self::FederatedUser(ref inner) => Display::fmt(inner, f),
420            Self::RootUser(ref inner) => Display::fmt(inner, f),
421            Self::Service(ref inner) => Display::fmt(inner, f),
422            Self::User(ref inner) => Display::fmt(inner, f),
423        }
424    }
425}
426
427impl TryFrom<&PrincipalIdentity> for Arn {
428    type Error = PrincipalError;
429    fn try_from(p: &PrincipalIdentity) -> Result<Arn, Self::Error> {
430        match p {
431            PrincipalIdentity::AssumedRole(ref d) => Ok(d.into()),
432            PrincipalIdentity::CanonicalUser(_) => Err(PrincipalError::CannotConvertToArn),
433            PrincipalIdentity::FederatedUser(ref d) => Ok(d.into()),
434            PrincipalIdentity::RootUser(ref d) => Ok(d.into()),
435            PrincipalIdentity::Service(_) => Err(PrincipalError::CannotConvertToArn),
436            PrincipalIdentity::User(ref d) => Ok(d.into()),
437        }
438    }
439}
440
441impl TryFrom<PrincipalIdentity> for Arn {
442    type Error = PrincipalError;
443    fn try_from(p: PrincipalIdentity) -> Result<Arn, Self::Error> {
444        match p {
445            PrincipalIdentity::AssumedRole(ref d) => Ok(d.into()),
446            PrincipalIdentity::CanonicalUser(_) => Err(PrincipalError::CannotConvertToArn),
447            PrincipalIdentity::FederatedUser(ref d) => Ok(d.into()),
448            PrincipalIdentity::RootUser(ref d) => Ok(d.into()),
449            PrincipalIdentity::Service(_) => Err(PrincipalError::CannotConvertToArn),
450            PrincipalIdentity::User(ref d) => Ok(d.into()),
451        }
452    }
453}
454
455#[cfg(test)]
456mod test {
457    use {
458        crate::{
459            AssumedRole, CanonicalUser, FederatedUser, Principal, PrincipalError, PrincipalIdentity, PrincipalSource,
460            RootUser, Service, User,
461        },
462        scratchstack_arn::Arn,
463        std::{
464            collections::hash_map::DefaultHasher,
465            hash::{Hash, Hasher},
466            io::Write,
467            str::FromStr,
468        },
469    };
470
471    #[test]
472    fn check_source_derived() {
473        let s1a = PrincipalSource::Aws;
474        let s1b = PrincipalSource::Aws;
475        let s2 = PrincipalSource::CanonicalUser;
476        let s3 = PrincipalSource::Federated;
477        let s4 = PrincipalSource::Service;
478
479        assert_eq!(s1a, s1b);
480        assert_ne!(s1a, s2);
481        assert_eq!(s1a.clone(), s1a);
482        assert_ne!(s1a, s3);
483        assert_ne!(s1a, s4);
484        assert_ne!(s2, s3);
485        assert_ne!(s2, s4);
486        assert_ne!(s3, s4);
487
488        // Ensure we can hash the source.
489        let mut h1a = DefaultHasher::new();
490        let mut h1b = DefaultHasher::new();
491        let mut h2 = DefaultHasher::new();
492        s1a.hash(&mut h1a);
493        s1b.hash(&mut h1b);
494        s2.hash(&mut h2);
495        let hash1a = h1a.finish();
496        let hash1b = h1b.finish();
497        let hash2 = h2.finish();
498        assert_eq!(hash1a, hash1b);
499        assert_ne!(hash1a, hash2);
500
501        // Ensure ordering is logical.
502        assert!(s1a <= s1b);
503        assert!(s1a < s2);
504        assert!(s2 < s3);
505        assert!(s3 < s4);
506        assert_eq!(s1a.max(s2), s2);
507        assert_eq!(s1a.min(s2), s1a);
508
509        // Ensure we can debug print a source.
510        let _ = format!("{s1a:?}");
511    }
512
513    #[test]
514    fn check_hash_ord() {
515        let p1 = PrincipalIdentity::from(AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap());
516        let p2 = PrincipalIdentity::from(
517            CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap(),
518        );
519        let p3 = PrincipalIdentity::from(FederatedUser::new("aws", "123456789012", "user@domain").unwrap());
520        let p4 = PrincipalIdentity::from(RootUser::new("aws", "123456789012").unwrap());
521        let p5 = PrincipalIdentity::from(Service::new("service-name", None, "amazonaws.com").unwrap());
522        let p6 = PrincipalIdentity::from(User::new("aws", "123456789012", "/", "user-name").unwrap());
523
524        let mut h1 = DefaultHasher::new();
525        let mut h2 = DefaultHasher::new();
526        let mut h3 = DefaultHasher::new();
527        let mut h4 = DefaultHasher::new();
528        let mut h5 = DefaultHasher::new();
529        let mut h6 = DefaultHasher::new();
530        p1.hash(&mut h1);
531        p2.hash(&mut h2);
532        p3.hash(&mut h3);
533        p4.hash(&mut h4);
534        p5.hash(&mut h5);
535        p6.hash(&mut h6);
536        let hash1 = h1.finish();
537        let hash2 = h2.finish();
538        let hash3 = h3.finish();
539        let hash4 = h4.finish();
540        let hash5 = h5.finish();
541        let hash6 = h6.finish();
542        assert_ne!(hash1, hash2);
543        assert_ne!(hash1, hash3);
544        assert_ne!(hash1, hash4);
545        assert_ne!(hash1, hash5);
546        assert_ne!(hash1, hash6);
547        assert_ne!(hash2, hash3);
548        assert_ne!(hash2, hash4);
549        assert_ne!(hash2, hash5);
550        assert_ne!(hash2, hash6);
551        assert_ne!(hash3, hash4);
552        assert_ne!(hash3, hash5);
553        assert_ne!(hash3, hash6);
554        assert_ne!(hash4, hash5);
555        assert_ne!(hash4, hash6);
556        assert_ne!(hash5, hash6);
557
558        assert!(p1 < p2);
559        assert!(p1 < p3);
560        assert!(p1 < p4);
561        assert!(p1 < p5);
562        assert_eq!(p1.clone().max(p2.clone()), p2);
563        assert_eq!(p1.clone().min(p2), p1);
564    }
565
566    #[test]
567    fn check_assumed_role() {
568        let r1a = AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap();
569
570        let r1b = AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap();
571
572        let r2 =
573            AssumedRole::new("aws2", "123456789012", "Role@Foo=bar,baz_=world-1234", "Session@1234,_=-,.OK").unwrap();
574
575        let p1a = PrincipalIdentity::from(r1a);
576        let p1b = PrincipalIdentity::from(r1b);
577        let p2 = PrincipalIdentity::from(r2);
578
579        assert_eq!(p1a, p1b);
580        assert_ne!(p1a, p2);
581        assert_eq!(p1a, p1a.clone());
582
583        assert_eq!(p1a.to_string(), "arn:aws:sts::123456789012:assumed-role/Role_name/session_name");
584        assert_eq!(p1a.source(), PrincipalSource::Aws);
585        assert!(p1a.has_arn());
586
587        // Make sure we can debug the assumed role
588        let _ = format!("{p1a:?}");
589
590        let arn: Arn = (&p1a).try_into().unwrap();
591        assert_eq!(arn.partition(), "aws");
592        assert_eq!(arn.service(), "sts");
593        assert_eq!(arn.region(), "");
594        assert_eq!(arn.account_id(), "123456789012");
595        assert_eq!(arn.resource(), "assumed-role/Role_name/session_name");
596
597        let arn: Arn = p1a.try_into().unwrap();
598        assert_eq!(arn.partition(), "aws");
599        assert_eq!(arn.service(), "sts");
600        assert_eq!(arn.region(), "");
601        assert_eq!(arn.account_id(), "123456789012");
602        assert_eq!(arn.resource(), "assumed-role/Role_name/session_name");
603    }
604
605    #[test]
606    fn check_canonical_user() {
607        let cu1a = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
608        let cu1b = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
609        let cu2 = CanonicalUser::new("772183b840c93fe103e45cd24ca8b8c94425a373465c6eb535b7c4b9593811e5").unwrap();
610
611        let p1a = PrincipalIdentity::from(cu1a);
612        let p1b = PrincipalIdentity::from(cu1b);
613        let p2 = PrincipalIdentity::from(cu2);
614
615        assert_eq!(p1a, p1b);
616        assert_ne!(p1a, p2);
617        assert_eq!(p1a, p1a.clone());
618
619        assert_eq!(p1a.to_string(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");
620        assert_eq!(p1a.source(), PrincipalSource::CanonicalUser);
621        assert!(!p1a.has_arn());
622
623        // Make sure we can debug the canonical user
624        let _ = format!("{p1a:?}");
625
626        let err = TryInto::<Arn>::try_into(&p1a).unwrap_err();
627        assert_eq!(err, PrincipalError::CannotConvertToArn);
628        assert_eq!(err.to_string(), "Cannot convert entity to ARN");
629
630        let err = TryInto::<Arn>::try_into(p1a).unwrap_err();
631        assert_eq!(err, PrincipalError::CannotConvertToArn);
632        assert_eq!(err.to_string(), "Cannot convert entity to ARN");
633    }
634
635    #[test]
636    fn check_federated_user() {
637        let f1a = FederatedUser::new("aws", "123456789012", "user@domain").unwrap();
638        let f1b = FederatedUser::new("aws", "123456789012", "user@domain").unwrap();
639        let f2 = FederatedUser::new("partition-with-32-characters1234", "123456789012", "user@domain").unwrap();
640
641        let p1a = PrincipalIdentity::from(f1a);
642        let p1b = PrincipalIdentity::from(f1b);
643        let p2 = PrincipalIdentity::from(f2);
644
645        assert_eq!(p1a, p1b);
646        assert_ne!(p1a, p2);
647        assert_eq!(p1a, p1a.clone());
648
649        assert_eq!(p1a.to_string(), "arn:aws:sts::123456789012:federated-user/user@domain");
650        assert_eq!(p1a.source(), PrincipalSource::Federated);
651        assert!(p1a.has_arn());
652
653        // Make sure we can debug the federated user
654        let _ = format!("{p1a:?}");
655
656        let arn: Arn = (&p1a).try_into().unwrap();
657        assert_eq!(arn.partition(), "aws");
658        assert_eq!(arn.service(), "sts");
659        assert_eq!(arn.region(), "");
660        assert_eq!(arn.account_id(), "123456789012");
661        assert_eq!(arn.resource(), "federated-user/user@domain");
662
663        let arn: Arn = p1a.try_into().unwrap();
664        assert_eq!(arn.partition(), "aws");
665        assert_eq!(arn.service(), "sts");
666        assert_eq!(arn.region(), "");
667        assert_eq!(arn.account_id(), "123456789012");
668        assert_eq!(arn.resource(), "federated-user/user@domain");
669    }
670
671    #[test]
672    fn check_root_user() {
673        let r1a = RootUser::new("aws", "123456789012").unwrap();
674        let r1b = RootUser::new("aws", "123456789012").unwrap();
675        let r2 = RootUser::new("aws", "123456789099").unwrap();
676
677        let p1a = PrincipalIdentity::from(r1a);
678        let p1b = PrincipalIdentity::from(r1b);
679        let p2 = PrincipalIdentity::from(r2);
680
681        assert_eq!(p1a, p1b);
682        assert_ne!(p1a, p2);
683        assert_eq!(p1a, p1a.clone());
684
685        assert_eq!(p1a.to_string(), "123456789012");
686        assert_eq!(p1a.source(), PrincipalSource::Aws);
687        assert!(p1a.has_arn());
688
689        // Make sure we can debug the root user
690        let _ = format!("{p1a:?}");
691
692        let arn: Arn = (&p1a).try_into().unwrap();
693        assert_eq!(arn.partition(), "aws");
694        assert_eq!(arn.service(), "iam");
695        assert_eq!(arn.region(), "");
696        assert_eq!(arn.account_id(), "123456789012");
697        assert_eq!(arn.resource(), "root");
698
699        let arn: Arn = p1a.try_into().unwrap();
700        assert_eq!(arn.partition(), "aws");
701        assert_eq!(arn.service(), "iam");
702        assert_eq!(arn.region(), "");
703        assert_eq!(arn.account_id(), "123456789012");
704        assert_eq!(arn.resource(), "root");
705    }
706
707    #[test]
708    fn check_service() {
709        let s1a = Service::new("service-name", None, "amazonaws.com").unwrap();
710        let s1b = Service::new("service-name", None, "amazonaws.com").unwrap();
711        let s2 = Service::new("service-name2", None, "amazonaws.com").unwrap();
712
713        let p1a = PrincipalIdentity::from(s1a);
714        let p1b = PrincipalIdentity::from(s1b);
715        let p2 = PrincipalIdentity::from(s2);
716
717        assert_eq!(p1a, p1b);
718        assert_ne!(p1a, p2);
719        assert_eq!(p1a, p1a.clone());
720
721        assert_eq!(p1a.to_string(), "service-name.amazonaws.com");
722        assert_eq!(p1a.source(), PrincipalSource::Service);
723        assert!(!p1a.has_arn());
724
725        // Make sure we can debug the root user
726        let _ = format!("{p1a:?}");
727
728        let err = TryInto::<Arn>::try_into(&p1a).unwrap_err();
729        assert_eq!(err, PrincipalError::CannotConvertToArn);
730
731        let err = TryInto::<Arn>::try_into(p1a).unwrap_err();
732        assert_eq!(err, PrincipalError::CannotConvertToArn);
733    }
734
735    #[test]
736    fn check_user() {
737        let u1a = User::new("aws", "123456789012", "/", "user-name").unwrap();
738        let u1b = User::new("aws", "123456789012", "/", "user-name").unwrap();
739        let u2 = User::new("aws", "123456789012", "/", "user-name2").unwrap();
740
741        let p1a = PrincipalIdentity::from(u1a);
742        let p1b = PrincipalIdentity::from(u1b);
743        let p2 = PrincipalIdentity::from(u2);
744
745        assert_eq!(p1a, p1b);
746        assert_ne!(p1a, p2);
747        assert_eq!(p1a, p1a.clone());
748
749        assert_eq!(p1a.to_string(), "arn:aws:iam::123456789012:user/user-name");
750        assert_eq!(p1a.source(), PrincipalSource::Aws);
751        assert!(p1a.has_arn());
752
753        // Make sure we can debug the root user
754        let _ = format!("{p1a:?}");
755
756        let arn: Arn = (&p1a).try_into().unwrap();
757        assert_eq!(arn.partition(), "aws");
758        assert_eq!(arn.service(), "iam");
759        assert_eq!(arn.region(), "");
760        assert_eq!(arn.account_id(), "123456789012");
761        assert_eq!(arn.resource(), "user/user-name");
762
763        let arn: Arn = p1a.try_into().unwrap();
764        assert_eq!(arn.partition(), "aws");
765        assert_eq!(arn.service(), "iam");
766        assert_eq!(arn.region(), "");
767        assert_eq!(arn.account_id(), "123456789012");
768        assert_eq!(arn.resource(), "user/user-name");
769    }
770
771    #[test]
772    fn check_principal_basics() {
773        let ar1a = AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap();
774        let ar1b = AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap();
775        let cu1a = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
776        let cu1b = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
777        let f1a = FederatedUser::new("aws", "123456789012", "user@domain").unwrap();
778        let f1b = FederatedUser::new("aws", "123456789012", "user@domain").unwrap();
779        let r1a = RootUser::new("aws", "123456789012").unwrap();
780        let r1b = RootUser::new("aws", "123456789012").unwrap();
781        let s1a = Service::new("service-name", None, "amazonaws.com").unwrap();
782        let s1b = Service::new("service-name", None, "amazonaws.com").unwrap();
783        let u1a = User::new("aws", "123456789012", "/", "user-name").unwrap();
784        let u1b = User::new("aws", "123456789012", "/", "user-name").unwrap();
785        let u2 = User::new("aws", "123456789012", "/", "user-name2").unwrap();
786
787        let principals1a = vec![
788            PrincipalIdentity::from(ar1a.clone()),
789            PrincipalIdentity::from(cu1a.clone()),
790            PrincipalIdentity::from(f1a.clone()),
791            PrincipalIdentity::from(r1a.clone()),
792            PrincipalIdentity::from(s1a.clone()),
793            PrincipalIdentity::from(u1a.clone()),
794            PrincipalIdentity::from(ar1b),
795            PrincipalIdentity::from(cu1b),
796            PrincipalIdentity::from(f1b),
797            PrincipalIdentity::from(r1b),
798            PrincipalIdentity::from(s1b),
799            PrincipalIdentity::from(u1b),
800        ];
801        let principals1b = vec![
802            PrincipalIdentity::from(u1a.clone()),
803            PrincipalIdentity::from(s1a.clone()),
804            PrincipalIdentity::from(r1a.clone()),
805            PrincipalIdentity::from(f1a.clone()),
806            PrincipalIdentity::from(cu1a.clone()),
807            PrincipalIdentity::from(ar1a.clone()),
808        ];
809        let principals2 = vec![
810            PrincipalIdentity::from(u1a.clone()),
811            PrincipalIdentity::from(s1a.clone()),
812            PrincipalIdentity::from(r1a.clone()),
813            PrincipalIdentity::from(f1a.clone()),
814            PrincipalIdentity::from(cu1a.clone()),
815            PrincipalIdentity::from(ar1a.clone()),
816            PrincipalIdentity::from(u2.clone()),
817        ];
818
819        let p1a = Principal::from(&principals1a);
820        let p1b = Principal::from(principals1b);
821        let mut p2 = Principal::from(principals2.as_slice());
822        let mut p3 = Principal::with_capacity(p1a.len());
823
824        assert!(!p1a.is_empty());
825        assert!(p3.is_empty());
826
827        assert_eq!(p1a, p1b);
828        assert_eq!(p1a, p1a.clone());
829        assert_ne!(p1a, p2);
830
831        assert_eq!(p1a.as_slice(), p1b.as_slice());
832
833        let p1adebug = format!("{p1a:?}");
834        let p1bdebug = format!("{p1b:?}");
835        assert_eq!(p1adebug, p1bdebug);
836
837        let p1adisplay = format!("{p1a}");
838        let p1bdisplay = format!("{p1b}");
839        assert_eq!(p1adisplay, p1bdisplay);
840
841        let mut p1a_emptied = p1a.clone();
842        p1a_emptied.clear();
843        assert_eq!(p1a_emptied, p3);
844
845        let p2b_slice: &[PrincipalIdentity] = p1b.as_ref();
846        for identity in p1a.iter() {
847            assert!(p2b_slice.contains(identity));
848        }
849
850        p3.add(u1a.into());
851        p3.add(s1a.into());
852        p3.add(r1a.into());
853        p3.add(f1a.into());
854        p3.add(cu1a.into());
855        p3.add(ar1a.into());
856
857        assert_eq!(p3, p1a);
858
859        // Ensure we can hash the principal.
860        let mut h1a = DefaultHasher::new();
861        let mut h1b = DefaultHasher::new();
862        let mut h2 = DefaultHasher::new();
863        p1a.hash(&mut h1a);
864        p1b.hash(&mut h1b);
865        p2.hash(&mut h2);
866        let hash1a = h1a.finish();
867        let hash1b = h1b.finish();
868        let hash2 = h2.finish();
869        assert_eq!(hash1a, hash1b);
870        assert_ne!(hash1a, hash2);
871
872        p2.remove(p2.len() - 1);
873        assert_eq!(p1a, p2);
874
875        p2.add(u2.into());
876        assert_ne!(p1a, p2);
877        p2.truncate(p2.len() - 1);
878        assert_eq!(p1a, p2);
879
880        (&p1a).into_iter().for_each(|identity| {
881            assert!(p1b.contains(identity));
882        });
883
884        p1a.into_iter().for_each(|identity| {
885            assert!(p1b.contains(&identity));
886        });
887    }
888
889    #[test]
890    fn failing_principal_display() {
891        let u1 = User::new("aws", "123456789012", "/", "user-name").unwrap();
892        let u2 = User::new("aws", "123456789012", "/", "user-name2").unwrap();
893
894        // Exercise the failing display code path.
895        for bufsize in 0..94 {
896            let mut buf = vec![0u8; bufsize];
897            let mut p: Principal = Default::default();
898            p.add(u1.clone().into());
899            p.add(u2.clone().into());
900            write!(buf.as_mut_slice(), "{p}").unwrap_err();
901        }
902    }
903
904    #[test]
905    fn test_conversions() {
906        let p = Principal::from(
907            AssumedRole::from_str("arn:aws:sts::123456789012:assumed-role/role-name/session-name").unwrap(),
908        );
909        assert_eq!(p.len(), 1);
910        assert!(p[0].as_assumed_role().is_some());
911        assert!(p[0].as_canonical_user().is_none());
912        assert!(p[0].as_federated_user().is_none());
913        assert!(p[0].as_root_user().is_none());
914        assert!(p[0].as_service().is_none());
915        assert!(p[0].as_user().is_none());
916
917        let p = Principal::from(
918            CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap(),
919        );
920        assert_eq!(p.len(), 1);
921        assert!(p[0].as_assumed_role().is_none());
922        assert!(p[0].as_canonical_user().is_some());
923        assert!(p[0].as_federated_user().is_none());
924        assert!(p[0].as_root_user().is_none());
925        assert!(p[0].as_service().is_none());
926        assert!(p[0].as_user().is_none());
927
928        let p = Principal::from(FederatedUser::new("aws", "123456789012", "dacut@kanga.org").unwrap());
929        assert_eq!(p.len(), 1);
930        assert!(p[0].as_assumed_role().is_none());
931        assert!(p[0].as_canonical_user().is_none());
932        assert!(p[0].as_federated_user().is_some());
933        assert!(p[0].as_root_user().is_none());
934        assert!(p[0].as_service().is_none());
935        assert!(p[0].as_user().is_none());
936
937        let p = Principal::from(RootUser::new("aws", "123456789012").unwrap());
938        assert_eq!(p.len(), 1);
939        assert!(p[0].as_assumed_role().is_none());
940        assert!(p[0].as_canonical_user().is_none());
941        assert!(p[0].as_federated_user().is_none());
942        assert!(p[0].as_root_user().is_some());
943        assert!(p[0].as_service().is_none());
944        assert!(p[0].as_user().is_none());
945
946        let p = Principal::from(Service::new("ec2", Some("us-west-2".to_string()), "amazonaws.com").unwrap());
947        assert_eq!(p.len(), 1);
948        assert!(p[0].as_assumed_role().is_none());
949        assert!(p[0].as_canonical_user().is_none());
950        assert!(p[0].as_federated_user().is_none());
951        assert!(p[0].as_root_user().is_none());
952        assert!(p[0].as_service().is_some());
953        assert!(p[0].as_user().is_none());
954
955        let p = Principal::from(User::from_str("arn:aws:iam::123456789012:user/user-name").unwrap());
956        assert_eq!(p.len(), 1);
957        assert!(p[0].as_assumed_role().is_none());
958        assert!(p[0].as_canonical_user().is_none());
959        assert!(p[0].as_federated_user().is_none());
960        assert!(p[0].as_root_user().is_none());
961        assert!(p[0].as_service().is_none());
962        assert!(p[0].as_user().is_some());
963
964        let p = Principal::from(vec![
965            PrincipalIdentity::parse_arn("arn:aws:sts::123456789012:assumed-role/role-name/session-name").unwrap(),
966            PrincipalIdentity::parse_arn("arn:aws:iam::123456789012:user/user-name").unwrap(),
967        ]);
968        assert_eq!(p.len(), 2);
969    }
970
971    #[test]
972    fn test_invalid_arns() {
973        let e =
974            PrincipalIdentity::parse_arn("arn:-aws:sts::123456789012:assumed-role/role-name/session-name").unwrap_err();
975        assert_eq!(e.to_string(), r#"Invalid partition: "-aws""#);
976
977        let e = PrincipalIdentity::parse_arn("arn:aws:sts:us-west-1:123456789012:assumed-role/role-name/session-name")
978            .unwrap_err();
979        assert_eq!(e.to_string(), r#"Invalid region: "us-west-1""#);
980
981        let e = PrincipalIdentity::parse_arn("arn:aws:sts::123456789012:role/role-name/session-name").unwrap_err();
982        assert_eq!(e.to_string(), r#"Invalid ARN: "arn:aws:sts::123456789012:role/role-name/session-name""#);
983
984        let e = PrincipalIdentity::parse_arn("arn:aws:iam:us-west-1:123456789012:user/path/user-name").unwrap_err();
985        assert_eq!(e.to_string(), r#"Invalid region: "us-west-1""#);
986
987        let e = PrincipalIdentity::parse_arn("arn:aws:iam::123456789012:role/role-name/session-name").unwrap_err();
988        assert_eq!(e.to_string(), r#"Invalid ARN: "arn:aws:iam::123456789012:role/role-name/session-name""#);
989
990        let e = PrincipalIdentity::parse_arn("arn:aws:s3::123456789012:role/role-name").unwrap_err();
991        assert_eq!(e.to_string(), r#"Invalid ARN: "arn:aws:s3::123456789012:role/role-name""#);
992    }
993}