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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
pub mod credentials;
pub mod error;
mod misc;
pub mod token;
pub mod token_source;
use crate::credentials::CredentialsFile;
use crate::misc::EMPTY;
use crate::token_source::authorized_user_token_source::UserAccountTokenSource;
use crate::token_source::compute_token_source::ComputeTokenSource;
use crate::token_source::reuse_token_source::ReuseTokenSource;
use crate::token_source::service_account_token_source::OAuth2ServiceAccountTokenSource;
use crate::token_source::service_account_token_source::ServiceAccountTokenSource;
use crate::token_source::TokenSource;
use google_cloud_metadata::on_gce;
const SERVICE_ACCOUNT_KEY: &str = "service_account";
const USER_CREDENTIALS_KEY: &str = "authorized_user";
pub struct Config<'a> {
pub audience: Option<&'a str>,
pub scopes: Option<&'a [&'a str]>,
}
impl Config<'_> {
pub fn scopes_to_string(&self, sep: &str) -> String {
match self.scopes {
Some(s) => s.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(sep),
None => EMPTY.to_string(),
}
}
}
#[derive(Clone)]
pub struct ProjectInfo {
pub project_id: Option<String>,
}
#[derive(Clone)]
pub enum Project {
FromFile(Box<CredentialsFile>),
FromMetadataServer(ProjectInfo),
}
impl std::fmt::Debug for Project {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Project::FromFile(_) => write!(f, "Project::FromFile"),
Project::FromMetadataServer(_) => write!(f, "Project::FromMetadataServer"),
}
}
}
impl Project {
pub fn project_id(&self) -> Option<&String> {
match self {
Self::FromFile(file) => file.project_id.as_ref(),
Self::FromMetadataServer(info) => info.project_id.as_ref(),
}
}
}
pub async fn project() -> Result<Project, error::Error> {
let credentials = credentials::CredentialsFile::new().await;
match credentials {
Ok(credentials) => Ok(Project::FromFile(Box::new(credentials))),
Err(e) => {
if on_gce().await {
let project_id = google_cloud_metadata::project_id().await;
Ok(Project::FromMetadataServer(ProjectInfo {
project_id: if project_id.is_empty() { None } else { Some(project_id) },
}))
} else {
Err(e)
}
}
}
}
pub async fn create_token_source_from_project(
project: &Project,
config: Config<'_>,
) -> Result<Box<dyn TokenSource>, error::Error> {
match project {
Project::FromFile(file) => {
let ts = credentials_from_json_with_params(file, &config)?;
let token = ts.token().await?;
Ok(Box::new(ReuseTokenSource::new(ts, token)))
}
Project::FromMetadataServer(_) => {
let ts = ComputeTokenSource::new(&config.scopes_to_string(","))?;
let token = ts.token().await?;
Ok(Box::new(ReuseTokenSource::new(Box::new(ts), token)))
}
}
}
pub async fn create_token_source(config: Config<'_>) -> Result<Box<dyn TokenSource>, error::Error> {
let project = project().await?;
create_token_source_from_project(&project, config).await
}
fn credentials_from_json_with_params(
credentials: &CredentialsFile,
config: &Config,
) -> Result<Box<dyn TokenSource>, error::Error> {
match credentials.tp.as_str() {
SERVICE_ACCOUNT_KEY => {
match config.audience {
None => {
if config.scopes.is_none() {
return Err(error::Error::ScopeOrAudienceRequired);
}
let source =
OAuth2ServiceAccountTokenSource::new(credentials, config.scopes_to_string(" ").as_str())?;
Ok(Box::new(source))
}
Some(audience) => {
let source = ServiceAccountTokenSource::new(credentials, audience)?;
Ok(Box::new(source))
}
}
}
USER_CREDENTIALS_KEY => Ok(Box::new(UserAccountTokenSource::new(credentials)?)),
_ => Err(error::Error::UnsupportedAccountType(credentials.tp.to_string())),
}
}