graph_oauth/identity/credentials/
authorization_code_assertion_credential.rs1use std::collections::HashMap;
2use std::fmt::{Debug, Formatter};
3
4use async_trait::async_trait;
5use http::{HeaderMap, HeaderName, HeaderValue};
6use reqwest::IntoUrl;
7use url::Url;
8
9use uuid::Uuid;
10
11use graph_core::cache::{CacheStore, InMemoryCacheStore, TokenCache};
12use graph_core::http::{AsyncResponseConverterExt, ResponseConverterExt};
13use graph_core::identity::ForceTokenRefresh;
14use graph_error::{AuthExecutionError, AuthExecutionResult, IdentityResult, AF};
15
16use crate::identity::credentials::app_config::AppConfig;
17use crate::identity::{
18 AuthCodeAuthorizationUrlParameterBuilder, Authority, AzureCloudInstance,
19 ConfidentialClientApplication, Token, TokenCredentialExecutor, CLIENT_ASSERTION_TYPE,
20};
21use crate::oauth_serializer::{AuthParameter, AuthSerializer};
22
23credential_builder!(
24 AuthorizationCodeAssertionCredentialBuilder,
25 ConfidentialClientApplication<AuthorizationCodeAssertionCredential>
26);
27
28#[derive(Clone)]
37pub struct AuthorizationCodeAssertionCredential {
38 pub(crate) app_config: AppConfig,
39 pub(crate) authorization_code: Option<String>,
41 pub(crate) refresh_token: Option<String>,
44 pub(crate) code_verifier: Option<String>,
48 pub(crate) client_assertion_type: String,
50 pub(crate) client_assertion: String,
54 token_cache: InMemoryCacheStore<Token>,
55}
56
57impl Debug for AuthorizationCodeAssertionCredential {
58 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59 f.debug_struct("AuthorizationCodeAssertionCredential")
60 .field("app_config", &self.app_config)
61 .finish()
62 }
63}
64
65impl AuthorizationCodeAssertionCredential {
66 pub fn new(
67 client_id: impl TryInto<Uuid>,
68 authorization_code: impl AsRef<str>,
69 client_assertion: impl AsRef<str>,
70 redirect_uri: Option<impl IntoUrl>,
71 ) -> IdentityResult<AuthorizationCodeAssertionCredential> {
72 let redirect_uri = {
73 if let Some(redirect_uri) = redirect_uri {
74 redirect_uri.into_url().ok()
75 } else {
76 None
77 }
78 };
79
80 Ok(AuthorizationCodeAssertionCredential {
81 app_config: AppConfig::builder(client_id)
82 .redirect_uri_option(redirect_uri)
83 .build(),
84 authorization_code: Some(authorization_code.as_ref().to_owned()),
85 refresh_token: None,
86 code_verifier: None,
87 client_assertion_type: CLIENT_ASSERTION_TYPE.to_owned(),
88 client_assertion: client_assertion.as_ref().to_owned(),
89 token_cache: Default::default(),
90 })
91 }
92
93 pub fn builder(
94 client_id: impl TryInto<Uuid>,
95 authorization_code: impl AsRef<str>,
96 ) -> AuthorizationCodeAssertionCredentialBuilder {
97 AuthorizationCodeAssertionCredentialBuilder::new_with_auth_code(
98 AppConfig::new(client_id),
99 authorization_code,
100 )
101 }
102
103 pub fn authorization_url_builder(
104 client_id: impl TryInto<Uuid>,
105 ) -> AuthCodeAuthorizationUrlParameterBuilder {
106 AuthCodeAuthorizationUrlParameterBuilder::new(client_id)
107 }
108
109 fn execute_cached_token_refresh(&mut self, cache_id: String) -> AuthExecutionResult<Token> {
110 let response = self.execute()?;
111
112 if !response.status().is_success() {
113 return Err(AuthExecutionError::silent_token_auth(
114 response.into_http_response()?,
115 ));
116 }
117
118 let new_token: Token = response.json()?;
119 self.token_cache.store(cache_id, new_token.clone());
120
121 if new_token.refresh_token.is_some() {
122 self.refresh_token = new_token.refresh_token.clone();
123 }
124
125 Ok(new_token)
126 }
127
128 async fn execute_cached_token_refresh_async(
129 &mut self,
130 cache_id: String,
131 ) -> AuthExecutionResult<Token> {
132 let response = self.execute_async().await?;
133
134 if !response.status().is_success() {
135 return Err(AuthExecutionError::silent_token_auth(
136 response.into_http_response_async().await?,
137 ));
138 }
139
140 let new_token: Token = response.json().await?;
141
142 if new_token.refresh_token.is_some() {
143 self.refresh_token = new_token.refresh_token.clone();
144 }
145
146 self.token_cache.store(cache_id, new_token.clone());
147 Ok(new_token)
148 }
149}
150
151#[async_trait]
152impl TokenCache for AuthorizationCodeAssertionCredential {
153 type Token = Token;
154
155 fn get_token_silent(&mut self) -> Result<Self::Token, AuthExecutionError> {
156 let cache_id = self.app_config.cache_id.to_string();
157
158 match self.app_config.force_token_refresh {
159 ForceTokenRefresh::Never => {
160 if self.refresh_token.is_some() {
163 if let Ok(token) = self.execute_cached_token_refresh(cache_id.clone()) {
164 return Ok(token);
165 }
166 }
167
168 if let Some(token) = self.token_cache.get(cache_id.as_str()) {
169 if token.is_expired_sub(time::Duration::minutes(5)) {
170 if let Some(refresh_token) = token.refresh_token.as_ref() {
171 self.refresh_token = Some(refresh_token.to_owned());
172 }
173
174 self.execute_cached_token_refresh(cache_id)
175 } else {
176 Ok(token)
177 }
178 } else {
179 self.execute_cached_token_refresh(cache_id)
180 }
181 }
182 ForceTokenRefresh::Once | ForceTokenRefresh::Always => {
183 let token_result = self.execute_cached_token_refresh(cache_id);
184 if self.app_config.force_token_refresh == ForceTokenRefresh::Once {
185 self.app_config.force_token_refresh = ForceTokenRefresh::Never;
186 }
187 token_result
188 }
189 }
190 }
191
192 async fn get_token_silent_async(&mut self) -> Result<Self::Token, AuthExecutionError> {
193 let cache_id = self.app_config.cache_id.to_string();
194
195 match self.app_config.force_token_refresh {
196 ForceTokenRefresh::Never => {
197 if self.refresh_token.is_some() {
200 if let Ok(token) = self
201 .execute_cached_token_refresh_async(cache_id.clone())
202 .await
203 {
204 return Ok(token);
205 }
206 }
207
208 if let Some(old_token) = self.token_cache.get(cache_id.as_str()) {
209 if old_token.is_expired_sub(time::Duration::minutes(5)) {
210 if let Some(refresh_token) = old_token.refresh_token.as_ref() {
211 self.refresh_token = Some(refresh_token.to_owned());
212 }
213
214 self.execute_cached_token_refresh_async(cache_id).await
215 } else {
216 Ok(old_token.clone())
217 }
218 } else {
219 self.execute_cached_token_refresh_async(cache_id).await
220 }
221 }
222 ForceTokenRefresh::Once | ForceTokenRefresh::Always => {
223 let token_result = self.execute_cached_token_refresh_async(cache_id).await;
224 if self.app_config.force_token_refresh == ForceTokenRefresh::Once {
225 self.app_config.force_token_refresh = ForceTokenRefresh::Never;
226 }
227 token_result
228 }
229 }
230 }
231
232 fn with_force_token_refresh(&mut self, force_token_refresh: ForceTokenRefresh) {
233 self.app_config.force_token_refresh = force_token_refresh;
234 }
235}
236
237#[async_trait]
238impl TokenCredentialExecutor for AuthorizationCodeAssertionCredential {
239 fn form_urlencode(&mut self) -> IdentityResult<HashMap<String, String>> {
240 let mut serializer = AuthSerializer::new();
241 let client_id = self.app_config.client_id.to_string();
242 if client_id.is_empty() || self.app_config.client_id.is_nil() {
243 return AF::result(AuthParameter::ClientId);
244 }
245
246 if self.client_assertion.trim().is_empty() {
247 return AF::result(AuthParameter::ClientAssertion);
248 }
249
250 if self.client_assertion_type.trim().is_empty() {
251 self.client_assertion_type = CLIENT_ASSERTION_TYPE.to_owned();
252 }
253
254 serializer
255 .client_id(client_id.as_str())
256 .client_assertion(self.client_assertion.as_str())
257 .client_assertion_type(self.client_assertion_type.as_str())
258 .set_scope(self.app_config.scope.clone());
259
260 if let Some(redirect_uri) = self.app_config.redirect_uri.as_ref() {
261 serializer.redirect_uri(redirect_uri.as_str());
262 }
263
264 if let Some(code_verifier) = self.code_verifier.as_ref() {
265 serializer.code_verifier(code_verifier.as_ref());
266 }
267
268 if let Some(refresh_token) = self.refresh_token.as_ref() {
269 if refresh_token.trim().is_empty() {
270 return AF::msg_result(
271 AuthParameter::RefreshToken.alias(),
272 "refresh_token is empty - cannot be an empty string",
273 );
274 }
275
276 serializer
277 .refresh_token(refresh_token.as_ref())
278 .grant_type("refresh_token");
279
280 return serializer.as_credential_map(
281 vec![AuthParameter::Scope],
282 vec![
283 AuthParameter::RefreshToken,
284 AuthParameter::ClientId,
285 AuthParameter::GrantType,
286 AuthParameter::ClientAssertion,
287 AuthParameter::ClientAssertionType,
288 ],
289 );
290 } else if let Some(authorization_code) = self.authorization_code.as_ref() {
291 if authorization_code.trim().is_empty() {
292 return AF::msg_result(
293 AuthParameter::AuthorizationCode.alias(),
294 "authorization_code is empty - cannot be an empty string",
295 );
296 }
297
298 serializer
299 .authorization_code(authorization_code.as_str())
300 .grant_type("authorization_code");
301
302 return serializer.as_credential_map(
303 vec![AuthParameter::Scope, AuthParameter::CodeVerifier],
304 vec![
305 AuthParameter::AuthorizationCode,
306 AuthParameter::ClientId,
307 AuthParameter::GrantType,
308 AuthParameter::RedirectUri,
309 AuthParameter::ClientAssertion,
310 AuthParameter::ClientAssertionType,
311 ],
312 );
313 }
314
315 AF::msg_result(
316 format!(
317 "{} or {}",
318 AuthParameter::AuthorizationCode.alias(),
319 AuthParameter::RefreshToken.alias()
320 ),
321 "Either authorization code or refresh token is required",
322 )
323 }
324
325 fn client_id(&self) -> &Uuid {
326 &self.app_config.client_id
327 }
328
329 fn authority(&self) -> Authority {
330 self.app_config.authority.clone()
331 }
332
333 fn azure_cloud_instance(&self) -> AzureCloudInstance {
334 self.app_config.azure_cloud_instance
335 }
336
337 fn app_config(&self) -> &AppConfig {
338 &self.app_config
339 }
340}
341
342#[derive(Clone)]
343pub struct AuthorizationCodeAssertionCredentialBuilder {
344 credential: AuthorizationCodeAssertionCredential,
345}
346
347impl AuthorizationCodeAssertionCredentialBuilder {
348 pub fn new(
349 client_id: impl TryInto<Uuid>,
350 authorization_code: impl AsRef<str>,
351 ) -> AuthorizationCodeAssertionCredentialBuilder {
352 AuthorizationCodeAssertionCredentialBuilder::new_with_auth_code(
353 AppConfig::new(client_id),
354 authorization_code,
355 )
356 }
357
358 pub(crate) fn new_with_auth_code(
359 app_config: AppConfig,
360 authorization_code: impl AsRef<str>,
361 ) -> AuthorizationCodeAssertionCredentialBuilder {
362 Self {
363 credential: AuthorizationCodeAssertionCredential {
364 app_config,
365 authorization_code: Some(authorization_code.as_ref().to_owned()),
366 refresh_token: None,
367 code_verifier: None,
368 client_assertion_type: CLIENT_ASSERTION_TYPE.to_owned(),
369 client_assertion: String::new(),
370 token_cache: Default::default(),
371 },
372 }
373 }
374
375 #[cfg(feature = "interactive-auth")]
376 pub(crate) fn new_with_token(
377 app_config: AppConfig,
378 token: Token,
379 ) -> AuthorizationCodeAssertionCredentialBuilder {
380 let cache_id = app_config.cache_id.clone();
381 let mut token_cache = InMemoryCacheStore::new();
382 token_cache.store(cache_id, token);
383
384 Self {
385 credential: AuthorizationCodeAssertionCredential {
386 app_config,
387 authorization_code: None,
388 refresh_token: None,
389 code_verifier: None,
390 client_assertion_type: CLIENT_ASSERTION_TYPE.to_owned(),
391 client_assertion: String::new(),
392 token_cache,
393 },
394 }
395 }
396
397 pub(crate) fn from_assertion(
398 authorization_code: impl AsRef<str>,
399 assertion: impl AsRef<str>,
400 app_config: AppConfig,
401 ) -> AuthorizationCodeAssertionCredentialBuilder {
402 Self {
403 credential: AuthorizationCodeAssertionCredential {
404 app_config,
405 authorization_code: Some(authorization_code.as_ref().to_owned()),
406 refresh_token: None,
407 code_verifier: None,
408 client_assertion_type: CLIENT_ASSERTION_TYPE.to_owned(),
409 client_assertion: assertion.as_ref().to_owned(),
410 token_cache: Default::default(),
411 },
412 }
413 }
414
415 pub fn with_authorization_code<T: AsRef<str>>(&mut self, authorization_code: T) -> &mut Self {
416 self.credential.authorization_code = Some(authorization_code.as_ref().to_owned());
417 self
418 }
419
420 pub fn with_refresh_token<T: AsRef<str>>(&mut self, refresh_token: T) -> &mut Self {
421 self.credential.authorization_code = None;
422 self.credential.refresh_token = Some(refresh_token.as_ref().to_owned());
423 self
424 }
425
426 pub fn with_redirect_uri(&mut self, redirect_uri: Url) -> &mut Self {
427 self.credential.app_config.redirect_uri = Some(redirect_uri);
428 self
429 }
430
431 pub fn with_code_verifier<T: AsRef<str>>(&mut self, code_verifier: T) -> &mut Self {
432 self.credential.code_verifier = Some(code_verifier.as_ref().to_owned());
433 self
434 }
435
436 pub fn with_client_assertion<T: AsRef<str>>(&mut self, client_assertion: T) -> &mut Self {
437 self.credential.client_assertion = client_assertion.as_ref().to_owned();
438 self
439 }
440
441 pub fn with_client_assertion_type<T: AsRef<str>>(
442 &mut self,
443 client_assertion_type: T,
444 ) -> &mut Self {
445 self.credential.client_assertion_type = client_assertion_type.as_ref().to_owned();
446 self
447 }
448
449 pub fn credential(self) -> AuthorizationCodeAssertionCredential {
450 self.credential
451 }
452}
453
454impl Debug for AuthorizationCodeAssertionCredentialBuilder {
455 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
456 self.credential.fmt(f)
457 }
458}