1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use {
    crate::PrincipalError,
    std::fmt::{Display, Formatter, Result as FmtResult},
};

/// Details about an S3 canonical user.
///
/// CanonicalUser structs are immutable.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CanonicalUser {
    /// The canonical user id.
    canonical_user_id: String,
}

impl CanonicalUser {
    /// Create a [CanonicalUser] object.
    ///
    /// # Arguments
    ///
    /// * `canonical_user_id`: The canonical user id. This must be a 64 character hex string in lower-case form.
    ///
    /// If all of the requirements are met, a [CanonicalUser] object is returned.  Otherwise, a [PrincipalError]
    /// error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use scratchstack_aws_principal::CanonicalUser;
    /// let canonical_user = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
    /// assert_eq!(canonical_user.canonical_user_id(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");
    /// ```
    pub fn new(canonical_user_id: &str) -> Result<Self, PrincipalError> {
        if canonical_user_id.len() != 64 {
            return Err(PrincipalError::InvalidCanonicalUserId(canonical_user_id.to_string()));
        }

        for c in canonical_user_id.bytes() {
            if !matches!(c, b'0'..=b'9' | b'a'..=b'f') {
                return Err(PrincipalError::InvalidCanonicalUserId(canonical_user_id.to_string()));
            }
        }

        Ok(Self {
            canonical_user_id: canonical_user_id.into(),
        })
    }

    /// The canonical user id.
    #[inline]
    pub fn canonical_user_id(&self) -> &str {
        &self.canonical_user_id
    }
}

impl Display for CanonicalUser {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(self.canonical_user_id())
    }
}

#[cfg(test)]
mod tests {
    use {
        super::CanonicalUser,
        crate::{PrincipalIdentity, PrincipalSource},
        std::{
            collections::hash_map::DefaultHasher,
            hash::{Hash, Hasher},
        },
    };

    #[test]
    fn check_components() {
        let cu1a = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
        assert_eq!(cu1a.canonical_user_id(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");

        let p = PrincipalIdentity::from(cu1a);
        let source = p.source();
        assert_eq!(source, PrincipalSource::CanonicalUser);
        assert_eq!(source.to_string(), "CanonicalUser".to_string());
    }

    #[test]
    #[allow(clippy::redundant_clone)]
    fn check_derived() {
        let cu1a = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
        let cu1b = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
        let cu2 = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000001").unwrap();
        let cu3 = CanonicalUser::new("0000000000000000000000000000000000000000000000000000000000000002").unwrap();

        assert_eq!(cu1a, cu1b);
        assert_ne!(cu1a, cu2);
        assert_eq!(cu1a.clone(), cu1a);

        // Ensure we can hash a canonical user.
        let mut h1a = DefaultHasher::new();
        let mut h1b = DefaultHasher::new();
        let mut h2 = DefaultHasher::new();
        cu1a.hash(&mut h1a);
        cu1b.hash(&mut h1b);
        cu2.hash(&mut h2);
        let hash1a = h1a.finish();
        let hash1b = h1b.finish();
        let hash2 = h2.finish();
        assert_eq!(hash1a, hash1b);
        assert_ne!(hash1a, hash2);

        // Ensure ordering is logical.
        assert!(cu1a <= cu1b);
        assert!(cu1a < cu2);
        assert!(cu2 > cu1a);
        assert!(cu2 < cu3);
        assert!(cu3 > cu2);
        assert!(cu3 > cu1a);

        assert_eq!(cu1a.clone().max(cu2.clone()), cu2);
        assert_eq!(cu1a.clone().min(cu2), cu1a);

        // Ensure we can debug a canonical user.
        let _ = format!("{cu1a:?}");
    }

    #[test]
    fn check_valid_canonical_user() {
        let cu1a = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
        let cu1b = CanonicalUser::new("9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d").unwrap();
        let cu2 = CanonicalUser::new("772183b840c93fe103e45cd24ca8b8c94425a373465c6eb535b7c4b9593811e5").unwrap();

        assert_eq!(cu1a, cu1b);
        assert_eq!(cu1a, cu1a.clone());
        assert_ne!(cu1a, cu2);

        assert_eq!(cu1a.to_string(), "9da4bcba2132ad952bba3c8ecb37e668d99b310ce313da30c98aba4cdf009a7d");
        assert_eq!(cu2.to_string(), "772183b840c93fe103e45cd24ca8b8c94425a373465c6eb535b7c4b9593811e5");
    }

    #[test]
    fn check_invalid_canonical_users() {
        let err = CanonicalUser::new("123456789012345678901234567890123456789012345678901234567890123").unwrap_err();
        assert_eq!(
            err.to_string(),
            r#"Invalid canonical user id: "123456789012345678901234567890123456789012345678901234567890123""#
        );

        let err = CanonicalUser::new("12345678901234567890123456789012345678901234567890123456789012345").unwrap_err();
        assert_eq!(
            err.to_string(),
            r#"Invalid canonical user id: "12345678901234567890123456789012345678901234567890123456789012345""#
        );

        let err = CanonicalUser::new("123456789012345678901234567890123456789012345678901234567890AAAA").unwrap_err();
        assert_eq!(
            err.to_string(),
            r#"Invalid canonical user id: "123456789012345678901234567890123456789012345678901234567890AAAA""#
        );
    }
}
// end tests -- do not delete; needed for coverage.