scratchstack_aws_principal/
canonical_user.rs

1use {
2    crate::PrincipalError,
3    std::fmt::{Display, Formatter, Result as FmtResult},
4};
5
6/// Details about an S3 canonical user.
7///
8/// CanonicalUser structs are immutable.
9#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub struct CanonicalUser {
11    /// The canonical user id.
12    canonical_user_id: String,
13}
14
15impl CanonicalUser {
16    /// Create a [CanonicalUser] object.
17    ///
18    /// # Arguments
19    ///
20    /// * `canonical_user_id`: The canonical user id. This must be a 64 character hex string in lower-case form.
21    ///
22    /// If all of the requirements are met, a [CanonicalUser] object is returned.  Otherwise, a [PrincipalError]
23    /// error is returned.
24    ///
25    /// # Example
26    ///
27    /// ```
28    /// # use scratchstack_aws_principal::CanonicalUser;
29    /// let canonical_user = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
30    /// assert_eq!(canonical_user.canonical_user_id(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");
31    /// ```
32    pub fn new(canonical_user_id: &str) -> Result<Self, PrincipalError> {
33        if canonical_user_id.len() != 64 {
34            return Err(PrincipalError::InvalidCanonicalUserId(canonical_user_id.to_string()));
35        }
36
37        for c in canonical_user_id.bytes() {
38            if !matches!(c, b'0'..=b'9' | b'a'..=b'f') {
39                return Err(PrincipalError::InvalidCanonicalUserId(canonical_user_id.to_string()));
40            }
41        }
42
43        Ok(Self {
44            canonical_user_id: canonical_user_id.into(),
45        })
46    }
47
48    /// The canonical user id.
49    #[inline]
50    pub fn canonical_user_id(&self) -> &str {
51        &self.canonical_user_id
52    }
53}
54
55impl Display for CanonicalUser {
56    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
57        f.write_str(self.canonical_user_id())
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use {
64        super::CanonicalUser,
65        crate::{PrincipalIdentity, PrincipalSource},
66        std::{
67            collections::hash_map::DefaultHasher,
68            hash::{Hash, Hasher},
69        },
70    };
71
72    #[test]
73    fn check_components() {
74        let cu1a = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
75        assert_eq!(cu1a.canonical_user_id(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");
76
77        let p = PrincipalIdentity::from(cu1a);
78        let source = p.source();
79        assert_eq!(source, PrincipalSource::CanonicalUser);
80        assert_eq!(source.to_string(), "CanonicalUser".to_string());
81    }
82
83    #[test]
84    #[allow(clippy::redundant_clone)]
85    fn check_derived() {
86        let cu1a = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
87        let cu1b = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
88        let cu2 = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000001").unwrap();
89        let cu3 = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000002").unwrap();
90
91        assert_eq!(cu1a, cu1b);
92        assert_ne!(cu1a, cu2);
93        assert_eq!(cu1a.clone(), cu1a);
94
95        // Ensure we can hash a canonical user.
96        let mut h1a = DefaultHasher::new();
97        let mut h1b = DefaultHasher::new();
98        let mut h2 = DefaultHasher::new();
99        cu1a.hash(&mut h1a);
100        cu1b.hash(&mut h1b);
101        cu2.hash(&mut h2);
102        let hash1a = h1a.finish();
103        let hash1b = h1b.finish();
104        let hash2 = h2.finish();
105        assert_eq!(hash1a, hash1b);
106        assert_ne!(hash1a, hash2);
107
108        // Ensure ordering is logical.
109        assert!(cu1a <= cu1b);
110        assert!(cu1a < cu2);
111        assert!(cu2 > cu1a);
112        assert!(cu2 < cu3);
113        assert!(cu3 > cu2);
114        assert!(cu3 > cu1a);
115
116        assert_eq!(cu1a.clone().max(cu2.clone()), cu2);
117        assert_eq!(cu1a.clone().min(cu2), cu1a);
118
119        // Ensure we can debug a canonical user.
120        let _ = format!("{cu1a:?}");
121    }
122
123    #[test]
124    fn check_valid_canonical_user() {
125        let cu1a = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
126        let cu1b = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
127        let cu2 = CanonicalUser::new("772183b840c93fe103e45cd24ca8b8c94425a373465c6eb535b7c4b9593811e5").unwrap();
128
129        assert_eq!(cu1a, cu1b);
130        assert_eq!(cu1a, cu1a.clone());
131        assert_ne!(cu1a, cu2);
132
133        assert_eq!(cu1a.to_string(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");
134        assert_eq!(cu2.to_string(), "772183b840c93fe103e45cd24ca8b8c94425a373465c6eb535b7c4b9593811e5");
135    }
136
137    #[test]
138    fn check_invalid_canonical_users() {
139        let err = CanonicalUser::new("123456789012345678901234567890123456789012345678901234567890123").unwrap_err();
140        assert_eq!(
141            err.to_string(),
142            r#"Invalid canonical user id: "123456789012345678901234567890123456789012345678901234567890123""#
143        );
144
145        let err = CanonicalUser::new("12345678901234567890123456789012345678901234567890123456789012345").unwrap_err();
146        assert_eq!(
147            err.to_string(),
148            r#"Invalid canonical user id: "12345678901234567890123456789012345678901234567890123456789012345""#
149        );
150
151        let err = CanonicalUser::new("123456789012345678901234567890123456789012345678901234567890AAAA").unwrap_err();
152        assert_eq!(
153            err.to_string(),
154            r#"Invalid canonical user id: "123456789012345678901234567890123456789012345678901234567890AAAA""#
155        );
156    }
157}
158// end tests -- do not delete; needed for coverage.