tame_oauth/gcp/
end_user.rs1use super::TokenResponse;
2use crate::{
3 error::{self, Error},
4 id_token::{
5 AccessTokenResponse, IdTokenOrRequest, IdTokenProvider, IdTokenRequest, IdTokenResponse,
6 },
7 token::{RequestReason, Token, TokenOrRequest, TokenProvider},
8 token_cache::CachedTokenProvider,
9 IdToken,
10};
11
12pub type EndUserCredentials = CachedTokenProvider<EndUserCredentialsInner>;
16impl EndUserCredentials {
17 pub fn new(info: EndUserCredentialsInfo) -> Self {
18 CachedTokenProvider::wrap(EndUserCredentialsInner::new(info))
19 }
20}
21
22#[derive(serde::Deserialize, Debug, Clone)]
25pub struct EndUserCredentialsInfo {
26 pub client_id: String,
28 pub client_secret: String,
30 pub refresh_token: String,
32 #[serde(rename = "type")]
34 pub client_type: String,
35}
36
37impl EndUserCredentialsInfo {
38 pub fn deserialize<T>(key_data: T) -> Result<Self, Error>
42 where
43 T: AsRef<[u8]>,
44 {
45 let slice = key_data.as_ref();
46
47 let account_info: Self = serde_json::from_slice(slice)?;
48 Ok(account_info)
49 }
50}
51
52pub struct EndUserCredentialsInner {
56 info: EndUserCredentialsInfo,
57}
58
59impl std::fmt::Debug for EndUserCredentialsInner {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("EndUserCredentialsInner")
62 .finish_non_exhaustive()
63 }
64}
65
66impl EndUserCredentialsInner {
67 pub fn new(info: EndUserCredentialsInfo) -> Self {
68 Self { info }
69 }
70}
71
72#[derive(serde::Deserialize, Debug)]
73struct IdTokenResponseBody {
74 id_token: String,
76}
77
78impl EndUserCredentialsInner {
79 fn prepare_token_request(&self) -> Result<http::Request<Vec<u8>>, Error> {
80 let url = "https://oauth2.googleapis.com/token";
87
88 let body = url::form_urlencoded::Serializer::new(String::new())
90 .append_pair("client_id", &self.info.client_id)
91 .append_pair("client_secret", &self.info.client_secret)
92 .append_pair("grant_type", "refresh_token")
93 .append_pair("refresh_token", &self.info.refresh_token)
94 .finish();
95
96 let body = Vec::from(body);
97
98 let request = http::Request::builder()
99 .method("POST")
100 .uri(url)
101 .header(
102 http::header::CONTENT_TYPE,
103 "application/x-www-form-urlencoded",
104 )
105 .header(http::header::CONTENT_LENGTH, body.len())
106 .body(body)?;
107
108 Ok(request)
109 }
110}
111
112impl TokenProvider for EndUserCredentialsInner {
113 fn get_token_with_subject<'a, S, I, T>(
114 &self,
115 subject: Option<T>,
116 _scopes: I,
123 ) -> Result<TokenOrRequest, Error>
124 where
125 S: AsRef<str> + 'a,
126 I: IntoIterator<Item = &'a S>,
127 T: Into<String>,
128 {
129 if subject.is_some() {
131 return Err(Error::Auth(error::AuthError {
132 error: Some("Unsupported".to_string()),
133 error_description: Some(
134 "ADC / User tokens do not support jwt subjects".to_string(),
135 ),
136 }));
137 }
138
139 let request = self.prepare_token_request()?;
140
141 Ok(TokenOrRequest::Request {
142 request,
143 reason: RequestReason::ParametersChanged,
144 scope_hash: 0,
145 })
146 }
147
148 fn parse_token_response<S>(
149 &self,
150 _hash: u64,
151 response: http::Response<S>,
152 ) -> Result<Token, Error>
153 where
154 S: AsRef<[u8]>,
155 {
156 let (parts, body) = response.into_parts();
157
158 if !parts.status.is_success() {
159 return Err(Error::HttpStatus(parts.status));
160 }
161
162 let token_res: TokenResponse = serde_json::from_slice(body.as_ref())?;
164
165 let token: Token = token_res.into();
171 Ok(token)
172 }
173}
174
175impl IdTokenProvider for EndUserCredentialsInner {
176 fn get_id_token(&self, _audience: &str) -> Result<IdTokenOrRequest, Error> {
177 let request = self.prepare_token_request()?;
178
179 Ok(IdTokenOrRequest::IdTokenRequest {
180 request,
181 reason: RequestReason::ParametersChanged,
182 audience_hash: 0,
183 })
184 }
185
186 fn get_id_token_with_access_token<S>(
187 &self,
188 _audience: &str,
189 _response: AccessTokenResponse<S>,
190 ) -> Result<IdTokenRequest, Error>
191 where
192 S: AsRef<[u8]>,
193 {
194 Err(Error::Auth(error::AuthError {
197 error: Some("Unsupported".to_string()),
198 error_description: Some(
199 "User credentials id tokens via access token not supported".to_string(),
200 ),
201 }))
202 }
203
204 fn parse_id_token_response<S>(
205 &self,
206 _hash: u64,
207 response: IdTokenResponse<S>,
208 ) -> Result<IdToken, Error>
209 where
210 S: AsRef<[u8]>,
211 {
212 let (parts, body) = response.into_parts();
213
214 if !parts.status.is_success() {
215 let body_bytes = body.as_ref();
216
217 if parts
218 .headers
219 .get(http::header::CONTENT_TYPE)
220 .and_then(|ct| ct.to_str().ok())
221 == Some("application/json; charset=utf-8")
222 {
223 if let Ok(auth_error) = serde_json::from_slice::<error::AuthError>(body_bytes) {
224 return Err(Error::Auth(auth_error));
225 }
226 }
227
228 return Err(Error::HttpStatus(parts.status));
229 }
230
231 let token_res: IdTokenResponseBody = serde_json::from_slice(body.as_ref())?;
232 let token = IdToken::new(token_res.id_token)?;
233
234 Ok(token)
235 }
236}
237
238#[cfg(test)]
239mod test {
240 use super::*;
241
242 #[test]
243 fn end_user_credentials() {
244 let provider = EndUserCredentialsInner::new(EndUserCredentialsInfo {
245 client_id: "fake_client@domain.com".into(),
246 client_secret: "TOP_SECRET".into(),
247 refresh_token: "REFRESH_TOKEN".into(),
248 client_type: "authorized_user".into(),
249 });
250
251 let scopes = vec!["better_not_be_there"];
253
254 let token_or_req = provider
255 .get_token(&scopes)
256 .expect("Should have gotten a request");
257
258 match token_or_req {
259 TokenOrRequest::Token(_) => panic!("Shouldn't have gotten a token"),
260 TokenOrRequest::Request { request, .. } => {
261 assert_eq!(request.uri().host(), Some("oauth2.googleapis.com"));
263 assert_eq!(request.uri().query(), None);
265 }
266 }
267 }
268}