tame_oauth/gcp/
metadata_server.rs1use super::TokenResponse;
2use crate::{
3 error::{self, Error},
4 id_token::{IdTokenOrRequest, IdTokenProvider},
5 token::{RequestReason, Token, TokenOrRequest, TokenProvider},
6 token_cache::CachedTokenProvider,
7 IdToken,
8};
9
10const METADATA_URL: &str =
11 "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts";
12
13pub type MetadataServerProvider = CachedTokenProvider<MetadataServerProviderInner>;
17impl MetadataServerProvider {
18 pub fn new(account_name: Option<String>) -> Self {
19 CachedTokenProvider::wrap(MetadataServerProviderInner::new(account_name))
20 }
21}
22
23#[derive(Debug)]
27pub struct MetadataServerProviderInner {
28 account_name: String,
29}
30
31impl MetadataServerProviderInner {
32 pub fn new(account_name: Option<String>) -> Self {
33 Self {
34 account_name: account_name.unwrap_or_else(|| "default".into()),
35 }
36 }
37}
38
39impl TokenProvider for MetadataServerProviderInner {
40 fn get_token_with_subject<'a, S, I, T>(
41 &self,
42 subject: Option<T>,
43 scopes: I,
44 ) -> Result<TokenOrRequest, Error>
45 where
46 S: AsRef<str> + 'a,
47 I: IntoIterator<Item = &'a S>,
48 T: Into<String>,
49 {
50 if subject.is_some() {
52 return Err(Error::Auth(error::AuthError {
53 error: Some("Unsupported".to_string()),
54 error_description: Some(
55 "Metadata server tokens do not support jwt subjects".to_string(),
56 ),
57 }));
58 }
59
60 let mut url = format!("{}/{}/token", METADATA_URL, self.account_name);
63
64 let scopes_str = scopes
66 .into_iter()
67 .map(|s| s.as_ref())
68 .collect::<Vec<_>>()
69 .join(",");
70
71 if !scopes_str.is_empty() {
73 url.push_str("?scopes=");
74 url.push_str(&scopes_str);
75 }
76
77 let request = http::Request::builder()
78 .method("GET")
79 .uri(url)
80 .header("Metadata-Flavor", "Google")
83 .body(Vec::new())?;
84
85 Ok(TokenOrRequest::Request {
86 request,
87 reason: RequestReason::ParametersChanged,
88 scope_hash: 0,
89 })
90 }
91
92 fn parse_token_response<S>(
93 &self,
94 _hash: u64,
95 response: http::Response<S>,
96 ) -> Result<Token, Error>
97 where
98 S: AsRef<[u8]>,
99 {
100 let (parts, body) = response.into_parts();
101
102 if !parts.status.is_success() {
103 return Err(Error::HttpStatus(parts.status));
104 }
105
106 let token_res: TokenResponse = serde_json::from_slice(body.as_ref())?;
108
109 let token: Token = token_res.into();
111 Ok(token)
112 }
113}
114
115impl IdTokenProvider for MetadataServerProviderInner {
116 fn get_id_token(&self, audience: &str) -> Result<IdTokenOrRequest, error::Error> {
117 let url = format!(
118 "{}/{}/identity?audience={}",
119 METADATA_URL, self.account_name, audience,
120 );
121
122 let request = http::Request::builder()
123 .method("GET")
124 .uri(url)
125 .header("Metadata-Flavor", "Google")
126 .body(Vec::new())?;
127
128 Ok(IdTokenOrRequest::IdTokenRequest {
129 request,
130 reason: RequestReason::ParametersChanged,
131 audience_hash: 0,
132 })
133 }
134
135 fn parse_id_token_response<S>(
136 &self,
137 _hash: u64,
138 response: http::Response<S>,
139 ) -> Result<IdToken, Error>
140 where
141 S: AsRef<[u8]>,
142 {
143 let (parts, body) = response.into_parts();
144
145 if !parts.status.is_success() {
146 return Err(Error::HttpStatus(parts.status));
147 }
148
149 let token = IdToken::new(String::from_utf8_lossy(body.as_ref()).into_owned())?;
150
151 Ok(token)
152 }
153
154 fn get_id_token_with_access_token<S>(
155 &self,
156 _audience: &str,
157 _access_token_resp: crate::id_token::AccessTokenResponse<S>,
158 ) -> Result<crate::id_token::IdTokenRequest, Error>
159 where
160 S: AsRef<[u8]>,
161 {
162 Err(Error::Auth(error::AuthError {
165 error: Some("Unsupported".to_string()),
166 error_description: Some(
167 "Metadata server id tokens via access token not supported".to_string(),
168 ),
169 }))
170 }
171}
172
173#[cfg(test)]
174mod test {
175 use super::*;
176
177 #[test]
178 fn metadata_noscopes() {
179 let provider = MetadataServerProvider::new(None);
180
181 let scopes: &[&str] = &[];
182
183 let token_or_req = provider
184 .get_token(scopes)
185 .expect("Should have gotten a request");
186
187 match token_or_req {
188 TokenOrRequest::Token(_) => panic!("Shouldn't have gotten a token"),
189 TokenOrRequest::Request { request, .. } => {
190 assert_eq!(request.uri().host(), Some("metadata.google.internal"));
192 assert_eq!(request.uri().query(), None);
194 }
195 }
196 }
197
198 #[test]
199 fn metadata_with_scopes() {
200 let provider = MetadataServerProvider::new(None);
201
202 let scopes = ["scope1", "scope2"];
203
204 let token_or_req = provider
205 .get_token(&scopes)
206 .expect("Should have gotten a request");
207
208 match token_or_req {
209 TokenOrRequest::Token(_) => panic!("Shouldn't have gotten a token"),
210 TokenOrRequest::Request { request, .. } => {
211 assert_eq!(request.uri().host(), Some("metadata.google.internal"));
213 assert!(request.uri().query().is_some());
215
216 let query_string = request.uri().query().unwrap();
217 assert!(
221 query_string == "scopes=scope1,scope2"
222 || query_string == "scopes=scope2,scope1"
223 );
224 }
225 }
226 }
227
228 #[test]
229 fn wrapper_dispatch() {
230 let provider =
232 crate::gcp::TokenProviderWrapperInner::Metadata(MetadataServerProviderInner::new(None));
233
234 let scopes = ["scope1", "scope2"];
236
237 let token_or_req = provider
238 .get_token(&scopes)
239 .expect("Should have gotten a request");
240
241 match token_or_req {
242 TokenOrRequest::Token(_) => panic!("Shouldn't have gotten a token"),
243 TokenOrRequest::Request { request, .. } => {
244 assert_eq!(request.uri().host(), Some("metadata.google.internal"));
246 assert!(request.uri().query().is_some());
248
249 let query_string = request.uri().query().unwrap();
250 assert!(
254 query_string == "scopes=scope1,scope2"
255 || query_string == "scopes=scope2,scope1"
256 );
257 }
258 }
259 }
260}