-
Notifications
You must be signed in to change notification settings - Fork 38
/
config_default_credentials.rs
107 lines (94 loc) · 3.55 KB
/
config_default_credentials.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::sync::Arc;
use async_trait::async_trait;
use bytes::Bytes;
use http_body_util::Full;
use hyper::header::CONTENT_TYPE;
use hyper::{Method, Request};
use serde::Serialize;
use tokio::sync::RwLock;
use tracing::{debug, instrument, Level};
use crate::types::{AuthorizedUserRefreshToken, HttpClient, Token};
use crate::{Error, TokenProvider};
/// A token provider that uses the default user credentials
///
/// Reads credentials from `.config/gcloud/application_default_credentials.json`.
#[derive(Debug)]
pub struct ConfigDefaultCredentials {
client: HttpClient,
token: RwLock<Arc<Token>>,
credentials: AuthorizedUserRefreshToken,
}
impl ConfigDefaultCredentials {
/// Check for user credentials in the default location and try to deserialize them
pub async fn new() -> Result<Self, Error> {
let client = HttpClient::new()?;
Self::with_client(&client).await
}
pub(crate) async fn with_client(client: &HttpClient) -> Result<Self, Error> {
debug!("try to load credentials from {}", USER_CREDENTIALS_PATH);
let mut home = home::home_dir().ok_or(Error::Str("home directory not found"))?;
home.push(USER_CREDENTIALS_PATH);
let credentials = AuthorizedUserRefreshToken::from_file(&home)?;
debug!(project = ?credentials.quota_project_id, client = credentials.client_id, "found user credentials");
Ok(Self {
client: client.clone(),
token: RwLock::new(Self::fetch_token(&credentials, client).await?),
credentials,
})
}
#[instrument(level = Level::DEBUG, skip(cred, client))]
async fn fetch_token(
cred: &AuthorizedUserRefreshToken,
client: &HttpClient,
) -> Result<Arc<Token>, Error> {
client
.token(
&|| {
Request::builder()
.method(Method::POST)
.uri(DEFAULT_TOKEN_GCP_URI)
.header(CONTENT_TYPE, "application/json")
.body(Full::from(Bytes::from(
serde_json::to_vec(&RefreshRequest {
client_id: &cred.client_id,
client_secret: &cred.client_secret,
grant_type: "refresh_token",
refresh_token: &cred.refresh_token,
})
.unwrap(),
)))
.unwrap()
},
"ConfigDefaultCredentials",
)
.await
}
}
#[async_trait]
impl TokenProvider for ConfigDefaultCredentials {
async fn token(&self, _scopes: &[&str]) -> Result<Arc<Token>, Error> {
let token = self.token.read().await.clone();
if !token.has_expired() {
return Ok(token);
}
let mut locked = self.token.write().await;
let token = Self::fetch_token(&self.credentials, &self.client).await?;
*locked = token.clone();
Ok(token)
}
async fn project_id(&self) -> Result<Arc<str>, Error> {
self.credentials
.quota_project_id
.clone()
.ok_or(Error::Str("no project ID in user credentials"))
}
}
#[derive(Serialize, Debug)]
struct RefreshRequest<'a> {
client_id: &'a str,
client_secret: &'a str,
grant_type: &'a str,
refresh_token: &'a str,
}
const DEFAULT_TOKEN_GCP_URI: &str = "https://accounts.google.com/o/oauth2/token";
const USER_CREDENTIALS_PATH: &str = ".config/gcloud/application_default_credentials.json";