gcloud_storage/http/
service_account_client.rs

1use reqwest::Response;
2use std::sync::Arc;
3use token_source::TokenSource;
4
5use crate::http::Error;
6
7#[derive(Clone)]
8pub struct ServiceAccountClient {
9    ts: Option<Arc<dyn TokenSource>>,
10    v1_endpoint: String,
11    http: reqwest_middleware::ClientWithMiddleware,
12}
13
14impl ServiceAccountClient {
15    pub(crate) fn new(
16        ts: Option<Arc<dyn TokenSource>>,
17        endpoint: &str,
18        http: reqwest_middleware::ClientWithMiddleware,
19    ) -> Self {
20        Self {
21            ts,
22            v1_endpoint: format!("{endpoint}/v1"),
23            http,
24        }
25    }
26
27    #[cfg_attr(feature = "trace", tracing::instrument(skip_all))]
28    pub async fn sign_blob(&self, name: &str, payload: &[u8]) -> Result<Vec<u8>, Error> {
29        let url = format!("{}/{}:signBlob", self.v1_endpoint, name);
30        let request = SignBlobRequest { payload };
31        let request = self
32            .http
33            .post(url)
34            .json(&request)
35            .header("X-Goog-Api-Client", "rust")
36            .header(reqwest::header::USER_AGENT, "google-cloud-storage");
37        let request = match &self.ts {
38            Some(ts) => {
39                let token = ts.token().await.map_err(Error::TokenSource)?;
40                request.header(reqwest::header::AUTHORIZATION, token)
41            }
42            None => request,
43        };
44        let response = request.send().await?;
45        let response = ServiceAccountClient::check_response_status(response).await?;
46        Ok(response.json::<SignBlobResponse>().await?.signed_blob)
47    }
48
49    /// Checks whether an HTTP response is successful and returns it, or returns an error.
50    async fn check_response_status(response: Response) -> Result<Response, Error> {
51        // Check the status code, returning the response if it is not an error.
52        match response.error_for_status_ref() {
53            Ok(_) => Ok(response),
54            Err(error) => match response.text().await {
55                Ok(raw) => Err(Error::RawResponse(error, raw)),
56                Err(_) => Err(Error::HttpClient(error)),
57            },
58        }
59    }
60}
61
62#[derive(serde::Serialize)]
63struct SignBlobRequest<'a> {
64    #[serde(with = "super::base64")]
65    payload: &'a [u8],
66}
67
68#[derive(serde::Deserialize)]
69#[serde(rename_all = "camelCase")]
70struct SignBlobResponse {
71    #[serde(with = "super::base64")]
72    signed_blob: Vec<u8>,
73}
74
75#[cfg(test)]
76mod test {
77    use reqwest::Client;
78    use reqwest_middleware::ClientBuilder;
79    use serial_test::serial;
80
81    use google_cloud_auth::project::Config;
82    use google_cloud_auth::token::DefaultTokenSourceProvider;
83    use token_source::TokenSourceProvider;
84
85    use crate::http::service_account_client::ServiceAccountClient;
86
87    async fn client() -> (ServiceAccountClient, String) {
88        let tsp = DefaultTokenSourceProvider::new(
89            Config::default().with_scopes(&["https://www.googleapis.com/auth/cloud-platform"]),
90        )
91        .await
92        .unwrap();
93        let email = tsp.source_credentials.clone().unwrap().client_email.unwrap();
94        let ts = tsp.token_source();
95        (
96            ServiceAccountClient::new(
97                Some(ts),
98                "https://iamcredentials.googleapis.com",
99                ClientBuilder::new(Client::default()).build(),
100            ),
101            email,
102        )
103    }
104
105    /// IAM Service Account Credentials API is required
106    #[tokio::test]
107    #[serial]
108    pub async fn sign_blob_test() {
109        let (client, email) = client().await;
110        let body = vec![
111            71, 79, 79, 71, 52, 45, 82, 83, 65, 45, 83, 72, 65, 50, 53, 54, 10, 50, 48, 50, 50, 48, 55, 48, 57, 84, 50,
112            51, 52, 56, 48, 56, 90, 10, 50, 48, 50, 50, 48, 55, 48, 57, 47, 97, 117, 116, 111, 47, 115, 116, 111, 114,
113            97, 103, 101, 47, 103, 111, 111, 103, 52, 95, 114, 101, 113, 117, 101, 115, 116, 10, 98, 101, 97, 48, 48,
114            49, 100, 98, 48, 50, 97, 56, 98, 55, 101, 101, 54, 50, 102, 50, 54, 53, 99, 101, 50, 52, 54, 53, 51, 49,
115            97, 98, 50, 54, 101, 102, 49, 97, 48, 97, 99, 100, 102, 102, 55, 99, 54, 55, 49, 100, 101, 56, 49, 100, 56,
116            56, 98, 50, 56, 101, 55, 48, 98, 101,
117        ];
118        let data = client
119            .sign_blob(&format!("projects/-/serviceAccounts/{email}"), &body)
120            .await
121            .unwrap();
122        assert_eq!(256, data.len());
123    }
124}