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
use {
    crate::AspenError,
    lazy_static::lazy_static,
    regex::Regex,
    scratchstack_arn::Arn,
    scratchstack_aws_principal::{PrincipalIdentity, PrincipalSource},
    std::{
        fmt::{Display, Formatter, Result as FmtResult},
        str::FromStr,
    },
};

lazy_static! {
    static ref AWS_ACCOUNT_ID: Regex = Regex::new(r"^\d{12}$").unwrap();
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AwsPrincipal {
    Account(String),
    Any,
    Arn(Arn),
}

impl AwsPrincipal {
    pub fn matches(&self, identity: &PrincipalIdentity) -> bool {
        if identity.source() != PrincipalSource::Aws {
            return false;
        }

        match self {
            Self::Any => true,
            Self::Account(account_id) => {
                let identity_arn: Arn = identity.try_into().expect("AWS principal identity must have an ARN");
                identity_arn.account_id() == account_id
            }
            Self::Arn(arn) => {
                let identity_arn: Arn = identity.try_into().expect("AWS principal identity must have an ARN");
                match arn.resource() {
                    "root" => {
                        arn.partition() == identity_arn.partition()
                            && arn.service() == identity_arn.service()
                            && arn.region() == identity_arn.region()
                            && arn.account_id() == identity_arn.account_id()
                    }
                    _ => arn == &identity_arn,
                }
            }
        }
    }
}

impl Display for AwsPrincipal {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match self {
            Self::Account(account_id) => f.write_str(account_id),
            Self::Any => f.write_str("*"),
            Self::Arn(arn) => arn.fmt(f),
        }
    }
}

impl FromStr for AwsPrincipal {
    type Err = AspenError;

    fn from_str(s: &str) -> Result<Self, AspenError> {
        if s == "*" {
            Ok(Self::Any)
        } else if AWS_ACCOUNT_ID.is_match(s) {
            Ok(AwsPrincipal::Account(s.to_string()))
        } else {
            match Arn::from_str(s) {
                Ok(arn) => Ok(AwsPrincipal::Arn(arn)),
                Err(_) => Err(AspenError::InvalidPrincipal(s.to_string())),
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use {
        crate::AwsPrincipal,
        pretty_assertions::{assert_eq, assert_ne},
        scratchstack_aws_principal::{CanonicalUser, PrincipalIdentity, Service, User},
    };

    #[test_log::test]
    fn test_derived() {
        // Just need to verify clone works as expected.
        let ap1a = AwsPrincipal::Any;
        let ap1b = AwsPrincipal::Any;
        let ap2a = AwsPrincipal::Account("123456789012".to_string());
        let ap2b = AwsPrincipal::Account("123456789012".to_string());
        let ap3a = AwsPrincipal::Arn("arn:aws:iam::123456789012:root".parse().unwrap());
        let ap3b = AwsPrincipal::Arn("arn:aws:iam::123456789012:root".parse().unwrap());

        assert_eq!(ap1a, ap1b);
        assert_eq!(ap2a, ap2b);
        assert_eq!(ap3a, ap3b);
        assert_ne!(ap1a, ap2a);
        assert_ne!(ap1a, ap3a);
        assert_ne!(ap2a, ap3a);

        assert_eq!(ap1a.clone(), ap1a);
        assert_eq!(ap2a.clone(), ap2a);
        assert_eq!(ap3a.clone(), ap3a);
    }

    #[test_log::test]
    fn test_matches() {
        assert!(AwsPrincipal::Any
            .matches(&PrincipalIdentity::from(User::new("aws", "123456789012", "/", "testuser").unwrap())));
        assert!(AwsPrincipal::Account("123456789012".to_string())
            .matches(&PrincipalIdentity::from(User::new("aws", "123456789012", "/", "testuser").unwrap())));
        assert!(!AwsPrincipal::Account("567890123456".to_string())
            .matches(&PrincipalIdentity::from(User::new("aws", "123456789012", "/", "testuser").unwrap())));
        assert!(
            !AwsPrincipal::Any.matches(&PrincipalIdentity::from(Service::new("iam", None, "amazonaws.com").unwrap()))
        );
        assert!(!AwsPrincipal::Account("123456789012".to_string())
            .matches(&PrincipalIdentity::from(Service::new("iam", None, "amazonaws.com").unwrap())));
        assert!(!AwsPrincipal::Any.matches(&PrincipalIdentity::from(
            CanonicalUser::new("772183b840c93fe103e45cd24ca8b8c94425a373465c6eb535b7c4b9593811e5").unwrap()
        )));

        assert!(AwsPrincipal::Arn("arn:aws:iam::123456789012:root".parse().unwrap())
            .matches(&PrincipalIdentity::from(User::new("aws", "123456789012", "/", "testuser").unwrap())));
    }
}