Skip to main content

cts_common/auth/claims/
aud.rs

1use crate::auth::claims::common::ArrayOrValue;
2use serde::{Deserialize, Serialize};
3
4// The audience claim in an assertion (e.g. the `aud` claim in the JWT).
5/// This can be a single audience or multiple audiences.
6// If its a URL inner type then http://foo.com and http://foo.com/ should be considered equal.
7#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
8#[serde(transparent)]
9pub struct Audience(ArrayOrValue<String>);
10
11impl From<ArrayOrValue<String>> for Audience {
12    fn from(audience: ArrayOrValue<String>) -> Self {
13        Self(audience)
14    }
15}
16
17impl Audience {
18    /// Creates a new audience with a single value.
19    pub fn new(audience: impl Into<String>) -> Self {
20        Self(ArrayOrValue::single(audience.into()))
21    }
22
23    /// Adds an audience to the list of audiences.
24    pub fn add_audience(self, audience: impl Into<String>) -> Self {
25        Self(self.0.add(audience.into()))
26    }
27
28    pub fn contains(&self, audience: &str) -> bool {
29        self.0.contains(audience)
30    }
31}
32
33impl From<String> for Audience {
34    fn from(audience: String) -> Self {
35        Self::new(audience)
36    }
37}
38
39impl From<Vec<String>> for Audience {
40    fn from(audiences: Vec<String>) -> Self {
41        Self(ArrayOrValue::from_iter(audiences))
42    }
43}
44
45#[cfg(feature = "test_utils")]
46impl<T> fake::Dummy<T> for Audience {
47    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _rng: &mut R) -> Self {
48        Self::new("https://console-api.stashdata.net")
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use serde_json::json;
56
57    mod single {
58        use super::*;
59
60        #[test]
61        fn serializes_as_literal() {
62            let audience = Audience::new("http://example.com");
63            let serialized = serde_json::to_value(&audience).unwrap();
64            assert_eq!(serialized, json!("http://example.com"));
65        }
66
67        #[test]
68        fn deserializes_from_literal() {
69            let audience: Audience = serde_json::from_str(r#""http://example.com""#).unwrap();
70            assert_eq!(
71                audience.0,
72                ArrayOrValue::single("http://example.com".to_string())
73            );
74        }
75    }
76
77    mod multiple {
78        use super::*;
79
80        #[test]
81        fn serializes_as_array() {
82            let target_a = "http://example.com";
83            let target_b = "http://another-example.com";
84            let audience = Audience::new(target_a).add_audience(target_b);
85            let serialized = serde_json::to_value(&audience).unwrap();
86            assert!(
87                serialized == json!([target_a, target_b])
88                    || serialized == json!([target_b, target_a]),
89                "Serialized audience should be an array of two URLs, got: {serialized}"
90            );
91        }
92
93        #[test]
94        fn deserializes_from_array() {
95            let audience: Audience =
96                serde_json::from_str(r#"["http://example.com","http://another-example.com"]"#)
97                    .unwrap();
98            assert_eq!(
99                audience.0,
100                ArrayOrValue::from_iter(vec![
101                    "http://example.com".to_string(),
102                    "http://another-example.com".to_string(),
103                ])
104            );
105        }
106    }
107}