scratchstack_aws_principal/
root_user.rs

1use {
2    crate::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/// Details about an AWS account root user.
11///
12/// RootUser structs are immutable.
13#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct RootUser {
15    /// The partition this principal exists in.
16    partition: String,
17
18    /// The account id.
19    account_id: String,
20}
21
22impl RootUser {
23    /// Create a [RootUser] object, refering to an actor with root credentials for the specified
24    /// AWS account.
25    ///
26    /// # Arguments
27    ///
28    /// * `partition` - The partition this principal exists in.
29    /// * `account_id`: The 12 digit account id. This must be composed of 12 ASCII digits or a
30    ///     [PrincipalError::InvalidAccountId] error will be returned.
31    ///
32    /// # Return value
33    ///
34    /// If the requirement is met, a [RootUser] object is returned. Otherwise, a  [PrincipalError] error is returned.
35    pub fn new(partition: &str, account_id: &str) -> Result<Self, PrincipalError> {
36        validate_partition(partition)?;
37        validate_account_id(account_id)?;
38
39        Ok(Self {
40            partition: partition.into(),
41            account_id: account_id.into(),
42        })
43    }
44
45    /// The partition of the user.
46    #[inline]
47    pub fn partition(&self) -> &str {
48        &self.partition
49    }
50
51    /// The account id of the user.
52    #[inline]
53    pub fn account_id(&self) -> &str {
54        &self.account_id
55    }
56}
57
58impl From<&RootUser> for Arn {
59    fn from(root_user: &RootUser) -> Self {
60        Arn::new(&root_user.partition, "iam", "", &root_user.account_id, "root").unwrap()
61    }
62}
63
64impl Display for RootUser {
65    fn fmt(&self, f: &mut Formatter) -> FmtResult {
66        write!(f, "{}", self.account_id)
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use {
73        super::RootUser,
74        crate::{PrincipalIdentity, PrincipalSource},
75        scratchstack_arn::Arn,
76        std::{
77            collections::hash_map::DefaultHasher,
78            hash::{Hash, Hasher},
79        },
80    };
81
82    #[test]
83    fn check_components() {
84        let root_user = RootUser::new("aws", "123456789012").unwrap();
85        assert_eq!(root_user.partition(), "aws");
86        assert_eq!(root_user.account_id(), "123456789012");
87
88        let p = PrincipalIdentity::from(root_user);
89        let source = p.source();
90        assert_eq!(source, PrincipalSource::Aws);
91        assert_eq!(source.to_string(), "AWS");
92    }
93
94    #[test]
95    fn check_derived() {
96        let r1a = RootUser::new("aws", "123456789012").unwrap();
97        let r1b = RootUser::new("aws", "123456789012").unwrap();
98        let r2 = RootUser::new("aws", "123456789099").unwrap();
99        let r3 = RootUser::new("awt", "123456789099").unwrap();
100
101        // Ensure we can hash a root user.
102        let mut h1a = DefaultHasher::new();
103        let mut h1b = DefaultHasher::new();
104        r1a.hash(&mut h1a);
105        r1b.hash(&mut h1b);
106        assert_eq!(h1a.finish(), h1b.finish());
107
108        assert!(r1a <= r1b);
109        assert!(r1a < r2);
110        assert!(r2 > r1a);
111        assert!(r2 < r3);
112        assert!(r3 > r2);
113        assert!(r1a < r3);
114
115        assert!(r1a.clone().min(r2.clone()) == r1a);
116        assert!(r2.clone().max(r1a.clone()) == r2);
117
118        // Make sure we can debug a root user.
119        let _ = format!("{r1a:?}");
120    }
121
122    #[test]
123    fn check_valid_root_users() {
124        let r1a = RootUser::new("aws", "123456789012").unwrap();
125        let r1b = RootUser::new("aws", "123456789012").unwrap();
126        let r2 = RootUser::new("aws", "123456789099").unwrap();
127
128        assert_eq!(r1a, r1b);
129        assert_ne!(r1a, r2);
130        assert_eq!(r1a, r1a.clone());
131
132        assert_eq!(r1a.to_string(), "123456789012");
133        assert_eq!(r2.to_string(), "123456789099");
134
135        let arn1a: Arn = (&r1a).into();
136
137        assert_eq!(arn1a.partition(), "aws");
138        assert_eq!(arn1a.service(), "iam");
139        assert_eq!(arn1a.region(), "");
140        assert_eq!(arn1a.account_id(), "123456789012");
141        assert_eq!(arn1a.resource(), "root");
142    }
143
144    #[test]
145    fn check_invalid_root_users() {
146        assert_eq!(RootUser::new("", "123456789012",).unwrap_err().to_string(), r#"Invalid partition: """#);
147        assert_eq!(RootUser::new("aws", "",).unwrap_err().to_string(), r#"Invalid account id: """#);
148    }
149}
150// end tests -- do not delete; needed for coverage.