Skip to main content

gcloud_sdk/
id_token.rs

1use std::fmt;
2
3use crate::error::ErrorKind;
4
5/// The audience an ID token is minted for and checked against: the URL of a Cloud Run
6/// service or a Cloud Run function, a custom audience configured on the service, or the
7/// OAuth client ID of a resource behind Identity-Aware Proxy.
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub struct IdTokenAudience(String);
10
11impl IdTokenAudience {
12    pub fn new(audience: impl Into<String>) -> Self {
13        Self(audience.into())
14    }
15
16    pub fn as_str(&self) -> &str {
17        &self.0
18    }
19}
20
21impl fmt::Display for IdTokenAudience {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        f.write_str(&self.0)
24    }
25}
26
27/// The email of a service account, such as
28/// `invoker@my-project.iam.gserviceaccount.com`.
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct ServiceAccountEmail(String);
31
32impl ServiceAccountEmail {
33    pub fn new(email: impl Into<String>) -> Self {
34        Self(email.into())
35    }
36
37    /// The service account a `service_account_impersonation_url` names: the last path
38    /// segment, up to the `:generateAccessToken` method.
39    pub(crate) fn from_impersonation_url(url: &str) -> crate::error::Result<Self> {
40        let resource = url.rsplit('/').next().unwrap_or_default();
41        let email = resource.split(':').next().unwrap_or_default();
42        if email.is_empty() {
43            Err(ErrorKind::InvalidImpersonationUrl(url.to_string()).into())
44        } else {
45            Ok(Self::new(email))
46        }
47    }
48}
49
50impl fmt::Display for ServiceAccountEmail {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.write_str(&self.0)
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn impersonation_url_names_the_service_account_before_the_method() {
62        let url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/invoker@orders.iam.gserviceaccount.com:generateAccessToken";
63
64        assert_eq!(
65            ServiceAccountEmail::from_impersonation_url(url).unwrap(),
66            ServiceAccountEmail::new("invoker@orders.iam.gserviceaccount.com")
67        );
68    }
69
70    #[test]
71    fn impersonation_url_without_a_service_account_is_invalid() {
72        let url =
73            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/:generateAccessToken";
74
75        assert!(matches!(
76            ServiceAccountEmail::from_impersonation_url(url)
77                .unwrap_err()
78                .into_kind(),
79            ErrorKind::InvalidImpersonationUrl(_)
80        ));
81    }
82}