1use std::convert::TryFrom;
2use std::fmt::Debug;
3use std::ops::Add;
4use std::path::PathBuf;
5
6use async_trait::async_trait;
7use chrono::prelude::*;
8use secret_vault_value::SecretValue;
9
10pub mod auth_token_generator;
11pub mod credentials;
12pub mod metadata;
13
14pub use credentials::{from_env_var, from_well_known_file};
15use metadata::from_metadata;
16
17pub use credentials::{from_file, from_json};
18use tracing::*;
19
20mod ext_creds_source;
21mod gce;
22
23pub type BoxSource = Box<dyn Source + Send + Sync + 'static>;
24
25#[async_trait]
26pub trait Source {
27 async fn token(&self) -> crate::error::Result<Token>;
28}
29
30pub async fn create_source(
31 token_source_type: TokenSourceType,
32 token_scopes: Vec<String>,
33) -> crate::error::Result<BoxSource> {
34 match token_source_type {
35 TokenSourceType::Default => Ok(find_default(&token_scopes).await?),
36 TokenSourceType::Json(json) => Ok(from_json(json.as_bytes(), &token_scopes)?.into()),
37 TokenSourceType::File(path) => Ok(from_file(path, &token_scopes)?.into()),
38 TokenSourceType::MetadataServer => {
39 if let Some(src) = from_metadata(&token_scopes, "default".to_string()).await? {
40 Ok(src.into())
41 } else {
42 Err(crate::error::ErrorKind::TokenSource.into())
43 }
44 }
45 TokenSourceType::MetadataServerWithAccount(account) => {
46 if let Some(src) = from_metadata(&token_scopes, account).await? {
47 Ok(src.into())
48 } else {
49 Err(crate::error::ErrorKind::TokenSource.into())
50 }
51 }
52 TokenSourceType::ExternalSource(token_source) => Ok(token_source),
53 }
54}
55
56pub async fn find_default(token_scopes: &[String]) -> crate::error::Result<BoxSource> {
61 debug!("Finding default token for scopes: {:?}", token_scopes);
62
63 if let Some(src) = from_env_var(token_scopes)? {
64 debug!("Creating token based on environment variable: GOOGLE_APPLICATION_CREDENTIALS");
65 return Ok(src.into());
66 }
67 if let Some(src) = from_well_known_file(token_scopes)? {
68 debug!("Creating token based on standard config files such as application_default_credentials.json");
69 return Ok(src.into());
70 }
71 if let Some(src) = from_metadata(token_scopes, "default".to_string()).await? {
72 debug!("Creating token based on metadata server");
73 return Ok(src.into());
74 }
75 warn!("None of the possible sources detected for Google OAuth token");
76 Err(crate::error::ErrorKind::TokenSource.into())
77}
78
79#[derive(Debug, Clone)]
80pub struct Token {
81 pub token_type: String,
82 pub token: SecretValue,
83 pub expiry: DateTime<Utc>,
84}
85
86impl Token {
87 pub fn new(token_type: String, token: SecretValue, expiry: DateTime<Utc>) -> Self {
88 Self {
89 token_type,
90 token,
91 expiry,
92 }
93 }
94 pub fn header_value(&self) -> String {
95 format!("{} {}", self.token_type, self.token.as_sensitive_str())
96 }
97
98 pub async fn generate_for_scopes(
99 token_source_type: TokenSourceType,
100 token_scopes: Vec<String>,
101 ) -> crate::error::Result<Token> {
102 let token_source: BoxSource = create_source(token_source_type, token_scopes).await?;
103 token_source.token().await
104 }
105}
106
107impl TryFrom<TokenResponse> for Token {
108 type Error = crate::error::Error;
109
110 fn try_from(v: TokenResponse) -> Result<Self, Self::Error> {
111 if v.token_type.is_empty()
112 || v.access_token.as_sensitive_bytes().is_empty()
113 || v.expires_in == 0
114 {
115 Err(crate::error::ErrorKind::TokenData.into())
116 } else {
117 Ok(Token {
118 token_type: v.token_type,
119 token: v.access_token,
120 expiry: Utc::now().add(chrono::Duration::seconds(v.expires_in.try_into().unwrap())),
121 })
122 }
123 }
124}
125
126#[derive(Debug, serde::Deserialize)]
127struct TokenResponse {
128 token_type: String,
129 access_token: SecretValue,
130 expires_in: u64,
131}
132
133impl TryFrom<&str> for TokenResponse {
134 type Error = crate::error::Error;
135
136 fn try_from(v: &str) -> Result<Self, Self::Error> {
137 let resp = serde_json::from_str(v).map_err(crate::error::ErrorKind::TokenJson)?;
138 Ok(resp)
139 }
140}
141
142#[derive(Debug, Clone)]
143pub struct ExternalJwtFunctionSource<F, FN>
144where
145 F: std::future::Future<Output = crate::error::Result<Token>> + Send + Sync + 'static,
146 FN: Fn() -> F + Send + Sync,
147{
148 token_fn: FN,
149}
150
151impl<F, FN> ExternalJwtFunctionSource<F, FN>
152where
153 F: std::future::Future<Output = crate::error::Result<Token>> + Send + Sync + 'static,
154 FN: Fn() -> F + Send + Sync,
155{
156 pub fn new(token_fn: FN) -> Self {
157 Self { token_fn }
158 }
159}
160
161#[async_trait]
162impl<F, FN> Source for ExternalJwtFunctionSource<F, FN>
163where
164 F: std::future::Future<Output = crate::error::Result<Token>> + Send + Sync,
165 FN: Fn() -> F + Send + Sync,
166{
167 async fn token(&self) -> crate::error::Result<Token> {
168 (self.token_fn)().await
169 }
170}
171
172pub enum TokenSourceType {
173 Default,
174 Json(String),
175 File(PathBuf),
176 MetadataServer,
177 MetadataServerWithAccount(String),
178 ExternalSource(BoxSource),
179}
180
181impl Debug for TokenSourceType {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 TokenSourceType::Default => write!(f, "Default"),
185 TokenSourceType::Json(_) => write!(f, "Json"),
186 TokenSourceType::File(_) => write!(f, "File"),
187 TokenSourceType::MetadataServer => write!(f, "MetadataServer"),
188 TokenSourceType::MetadataServerWithAccount(_) => write!(f, "MetadataServerWithAccount"),
189 TokenSourceType::ExternalSource(_) => write!(f, "ExternalSource"),
190 }
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use super::*;
197
198 macro_rules! test_token_try_from {
199 () => {};
200 ($name:ident, $in:expr, $ok:expr; $($tt:tt)*) => {
201 #[test]
202 fn $name() {
203 assert_eq!(Token::try_from($in).is_ok(), $ok)
204 }
205 test_token_try_from!($($tt)*);
206 };
207 }
208
209 test_token_try_from!(
210 test_token_try_from_token_type,
211 TokenResponse {
212 token_type: String::new(),
213 access_token: "secret".into(),
214 expires_in: 1,
215 },
216 false;
217
218 test_token_try_from_access_token,
219 TokenResponse {
220 token_type: "type".into(),
221 access_token: "".into(),
222 expires_in: 1,
223 },
224 false;
225
226 test_token_try_from_expires_in,
227 TokenResponse {
228 token_type: "type".into(),
229 access_token: "secret".into(),
230 expires_in: 0,
231 },
232 false;
233
234 test_token_try_from_ok,
235 TokenResponse {
236 token_type: "type".into(),
237 access_token: "secret".into(),
238 expires_in: 1,
239 },
240 true;
241 );
242}