cts_common/auth/claims/
azp.rs

1use std::fmt::Display;
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// The Authorized Party (`azp`) claim indicates the type of credential that was used to federate the token.
7/// `OIDC` indicates that the token was federated from an OIDC provider.
8/// If an ID is also provided, this indicates that the token was federated from a specific provider, otherwise it was federated from the root provider.
9///
10/// ## Examples
11///
12/// | Value | Description |
13/// | --- | --- |
14/// | `OIDC` | Federated from the root provider. |
15/// | `OIDC|123e4567-e89b-12d3-a456-426614174000` | Federated provider with ID `123e4567-e89b-12d3-a456-426614174000`. |
16/// | `CSAK|NWMLYZSTECEIGLDL` | Federated from an Access Key with ID `123e4567-e89b-12d3-a456-426614174000`. |
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub enum Azp {
19    RootProvider,
20
21    /// The OIDC provider ID used when federating a 3rd party token.
22    Provider(Uuid),
23
24    /// The Access Key ID used when federating a token from an Access Key.
25    AccessKey(Uuid),
26}
27
28impl Azp {
29    /// Returns `true` if the `azp` is the root provider.
30    pub fn is_root_provider(&self) -> bool {
31        matches!(self, Self::RootProvider)
32    }
33
34    /// Returns `true` if the `azp` is a provider with a specific ID.
35    pub fn is_provider(&self) -> bool {
36        matches!(self, Self::Provider(_))
37    }
38
39    /// Returns `true` if the `azp` is an access key.
40    pub fn is_access_key(&self) -> bool {
41        matches!(self, Self::AccessKey(_))
42    }
43}
44
45impl Display for Azp {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            Self::RootProvider => write!(f, "OIDC"),
49            Self::Provider(provider_id) => write!(f, "OIDC|{provider_id}"),
50            Self::AccessKey(access_key_id) => write!(f, "CSAK|{access_key_id}"),
51        }
52    }
53}
54
55impl Serialize for Azp {
56    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57    where
58        S: serde::Serializer,
59    {
60        serializer.serialize_str(self.to_string().as_str())
61    }
62}
63
64impl<'de> Deserialize<'de> for Azp {
65    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66    where
67        D: serde::Deserializer<'de>,
68    {
69        let s = String::deserialize(deserializer)?;
70        if s == "OIDC" {
71            Ok(Self::RootProvider)
72        } else if let Some((prefix, id)) = s.split_once('|') {
73            match prefix {
74                "OIDC" => Ok(Self::Provider(
75                    Uuid::parse_str(id).map_err(serde::de::Error::custom)?,
76                )),
77                "CSAK" => Ok(Self::AccessKey(
78                    Uuid::parse_str(id).map_err(serde::de::Error::custom)?,
79                )),
80                _ => Err(serde::de::Error::custom(format!("Invalid azp value: {s}"))),
81            }
82        } else {
83            Err(serde::de::Error::custom(format!("Invalid azp value: {s}")))
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use serde_json::json;
92
93    mod root {
94        use super::*;
95
96        #[test]
97        fn serializes_as_oidc() {
98            let azp = Azp::RootProvider;
99            let serialized = serde_json::to_value(azp).unwrap();
100            assert_eq!(serialized, json!("OIDC"));
101        }
102
103        #[test]
104        fn deserializes_from_oidc() {
105            let json = json!("OIDC");
106            let azp: Azp = serde_json::from_value(json).unwrap();
107            assert_eq!(azp, Azp::RootProvider);
108        }
109    }
110
111    mod provider {
112        use super::*;
113
114        #[test]
115        fn serializes_with_provider_id() {
116            let uuid = Uuid::new_v4();
117            let azp = Azp::Provider(uuid);
118            let serialized = serde_json::to_value(azp).unwrap();
119            assert_eq!(serialized, json!(format!("OIDC|{uuid}")));
120        }
121
122        #[test]
123        fn deserializes_with_provider_id() {
124            let uuid = Uuid::new_v4();
125            let json = json!(format!("OIDC|{uuid}"));
126            let azp: Azp = serde_json::from_value(json).unwrap();
127            assert_eq!(azp, Azp::Provider(uuid));
128        }
129    }
130
131    mod access_key {
132        use super::*;
133
134        #[test]
135        fn serializes_with_access_key_id() {
136            let uuid = Uuid::new_v4();
137            let azp = Azp::AccessKey(uuid);
138            let serialized = serde_json::to_value(azp).unwrap();
139            assert_eq!(serialized, json!(format!("CSAK|{uuid}")));
140        }
141
142        #[test]
143        fn deserializes_with_access_key_id() {
144            let uuid = Uuid::new_v4();
145            let json = json!(format!("CSAK|{uuid}"));
146            let azp: Azp = serde_json::from_value(json).unwrap();
147            assert_eq!(azp, Azp::AccessKey(uuid));
148        }
149    }
150
151    mod invalid {
152        use super::*;
153
154        #[test]
155        fn deserializes_invalid_azp() {
156            let json = json!("INVALID|123e4567-e89b-12d3-a456-426614174000");
157            let result: Result<Azp, _> = serde_json::from_value(json);
158            assert!(result.is_err());
159        }
160
161        #[test]
162        fn deserializes_invalid_format() {
163            let json = json!("OIDC|INVALID_UUID");
164            let result: Result<Azp, _> = serde_json::from_value(json);
165            assert!(result.is_err());
166        }
167    }
168
169    mod empty {
170        use super::*;
171
172        #[test]
173        fn fails_on_empty_string() {
174            let json = json!("");
175            let result: Result<Azp, _> = serde_json::from_value(json);
176            assert!(result.is_err());
177        }
178
179        #[test]
180        fn fails_on_null() {
181            let json = json!(null);
182            let result: Result<Azp, _> = serde_json::from_value(json);
183            assert!(result.is_err());
184        }
185    }
186
187    mod malformed {
188        use super::*;
189
190        #[test]
191        fn fails_on_malformed_string() {
192            let json = json!("OIDC|123e4567-e89b-12d3-a456-426614174000|extra");
193            let result: Result<Azp, _> = serde_json::from_value(json);
194            assert!(result.is_err());
195        }
196
197        #[test]
198        fn fails_on_missing_prefix() {
199            let json = json!("123e4567-e89b-12d3-a456-426614174000");
200            let result: Result<Azp, _> = serde_json::from_value(json);
201            assert!(result.is_err());
202        }
203
204        #[test]
205        fn fails_on_invalid_uuid() {
206            let json = json!("OIDC|invalid-uuid");
207            let result: Result<Azp, _> = serde_json::from_value(json);
208            assert!(result.is_err());
209        }
210
211        #[test]
212        fn fails_on_invalid_access_key_uuid() {
213            let json = json!("CSAK|invalid-uuid");
214            let result: Result<Azp, _> = serde_json::from_value(json);
215            assert!(result.is_err());
216        }
217    }
218}