google_api_rust_client_unoffical/auth/
service_account.rs

1use std::fs;
2use std::path::PathBuf;
3
4use anyhow::{bail, Ok, Result};
5use chrono::{Local, Duration};
6use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
7use reqwest::header::{HeaderValue, CONTENT_TYPE, HeaderMap};
8use reqwest::Client;
9use serde::{Serialize, Deserialize};
10use serde_json::Value;
11
12use super::auth_error::AuthErrorResponse;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ServiceAccountCredentials {
16    r#type: String,
17    project_id: String,
18    private_key_id: String,
19    private_key: String,
20    client_email: String,
21    client_id: String,
22    auth_uri: String,
23    token_uri: String,
24    auth_provider_x509_cert_url: String,
25    client_x509_cert_url: String,
26    universe_domain: String,
27
28    #[serde(skip_serializing_if = "Option::is_none")]
29    token: Option<Token>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    scopes: Option<Vec<String>>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    sub: Option<String>
34}
35
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Token {
39    expiration_time: i64,
40    access_token: String,
41}
42
43impl ServiceAccountCredentials {
44    /// Create `ServiceAccountCredentials` from file.
45    ///
46    /// * `filepath` -  File path to the service account credential file. File should be valid JSON.
47    pub fn from_service_account_file(filepath: PathBuf) -> Result<Self> {
48        let credentials_json = fs::read_to_string(filepath)?;
49        Ok(serde_json::from_str::<ServiceAccountCredentials>(&credentials_json)?)
50    }
51
52    /// Create `ServiceAccountCredentials` from json string.
53    ///
54    /// * `credentials_json` -  Json string of the service account crendentials.
55    pub fn from_service_account_info(credentials_json: String) -> Result<Self>  {
56        Ok(serde_json::from_str::<ServiceAccountCredentials>(&credentials_json)?)
57    }
58
59    /// Add scopes to request the access token for.
60    ///
61    /// * `scopes` -  Scopes that your application needs access to. [OAuth 2.0 Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)
62    pub fn with_scopes(&self, scopes: Vec<&str>) -> Self {
63        let mut scoped_credentials = self.clone();
64        scoped_credentials.scopes = Some(scopes.into_iter().map(|s| s.to_owned()).collect());
65        scoped_credentials.token = None;
66        return scoped_credentials
67    }
68
69    /// Add subject to grants your application delegated access to a resource.
70    ///
71    /// * `sub` -  The email address of the user for which the application is requesting delegated access. Ensure that the service account is authorized in the [Domain-wide delegation](https://support.google.com/a/answer/162106) page of the Admin console for the user in the sub claim
72    pub fn with_subject(&self, subject: &str) -> Self {
73        let mut subjected_credential = self.clone();
74        subjected_credential.sub = Some(subject.to_owned());
75        subjected_credential.token = None;
76        return subjected_credential
77    }
78
79    /// Get an access token for the service account using the scopes and subject specified.
80    pub async fn get_access_token(&mut self) -> Result<String> {
81        let now = Local::now();
82        let iat = now.timestamp();
83
84        match self.token.clone() {
85            Some(token) => {
86                if iat > token.expiration_time {
87                    let jwt = self.make_assertion()?;
88                    let access_token = self.request_token(&jwt).await?;
89                    self.token = Some(Token{
90                        expiration_time: (now + Duration::minutes(58)).timestamp(),
91                        access_token: access_token.clone(),
92                    });
93                    return Ok(access_token);
94                } else {
95                    return Ok(token.access_token.clone());
96                }
97            },
98            None => {
99                let jwt = self.make_assertion()?;
100                let access_token = self.request_token(&jwt).await?;
101                self.token = Some(Token{
102                    expiration_time: (now + Duration::minutes(58)).timestamp(),
103                    access_token: access_token.clone(),
104                });
105                return Ok(access_token);
106            }
107        };
108    }
109
110    fn make_assertion(&self) -> Result<String> {
111        let scope: String = match self.scopes.clone() {
112            Some(scopes) => {
113                scopes.join(",")
114            },
115            None => {
116                "".to_owned()
117            },
118        };
119
120        let mut header = Header::new(Algorithm::RS256);
121        header.typ = Some("JWT".to_owned());
122        header.kid = Some("".to_owned());
123
124        let now = Local::now();
125        let iat = now.timestamp();
126        let exp = (now + Duration::hours(1)).timestamp();
127        let claims = Claims {
128            iss: self.client_id.clone(),
129            sub: self.sub.clone(),
130            aud: self.token_uri.clone(),
131            scope,
132            iat,
133            exp,
134        };
135
136        let jwt = encode(
137            &header,
138            &claims,
139            &EncodingKey::from_rsa_pem(self.private_key.as_bytes())?,
140        )?;
141
142        return Ok(jwt);
143    }
144
145
146    async fn request_token(&self, assertion: &str) -> Result<String> {
147        let client = Client::new();
148        let mut headers = HeaderMap::new();
149        headers.insert(
150            CONTENT_TYPE,
151            HeaderValue::from_static("application/x-www-form-urlencoded"),
152        );
153
154        let grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer".to_owned();
155
156        let body_encoded = url_encoded_data::stringify(&[
157            ("assertion", assertion),
158            ("grant_type", &grant_type)
159        ]);
160
161        let response = client
162            .post(self.token_uri.clone())
163            .headers(headers.clone())
164            .body(body_encoded)
165            .send()
166            .await?;
167
168        let status_code = response.status();
169        let body: String = response.text().await?;
170
171        if !status_code.is_success() {
172            let error_response: AuthErrorResponse = serde_json::from_str(&body).unwrap_or_default();
173            bail!(format!("Response Error: {}! Message: {}", error_response.error, error_response.error_description));
174        }
175
176        let v: Value = serde_json::from_str(&body)?;
177        if let Some(access_token) = v["access_token"].as_str() {
178            return Ok(access_token.to_owned());
179        } else {
180            bail!("Error parsing for access token!")
181        }
182    }
183}
184
185
186#[derive(Debug, Serialize, Deserialize)]
187struct Claims {
188    iss: String,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    sub: Option<String>,
191    aud: String,
192    scope: String,
193    iat: i64,
194    exp: i64,
195}