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