graph_oauth/identity/credentials/
authorization_code_certificate_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
16#[cfg(feature = "openssl")]
17use crate::identity::X509Certificate;
18
19use crate::identity::{
20 AppConfig, AuthCodeAuthorizationUrlParameterBuilder, Authority, AzureCloudInstance,
21 ConfidentialClientApplication, Token, TokenCredentialExecutor, CLIENT_ASSERTION_TYPE,
22};
23use crate::oauth_serializer::{AuthParameter, AuthSerializer};
24
25credential_builder!(
26 AuthorizationCodeCertificateCredentialBuilder,
27 ConfidentialClientApplication<AuthorizationCodeCertificateCredential>
28);
29
30#[derive(Clone)]
83pub struct AuthorizationCodeCertificateCredential {
84 pub(crate) app_config: AppConfig,
85 pub(crate) authorization_code: Option<String>,
87 pub(crate) refresh_token: Option<String>,
90 pub(crate) code_verifier: Option<String>,
94 pub(crate) client_assertion_type: String,
96 pub(crate) client_assertion: String,
100 token_cache: InMemoryCacheStore<Token>,
101}
102
103impl Debug for AuthorizationCodeCertificateCredential {
104 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105 f.debug_struct("AuthorizationCodeCertificateCredential")
106 .field("app_config", &self.app_config)
107 .finish()
108 }
109}
110impl AuthorizationCodeCertificateCredential {
111 pub fn new<T: AsRef<str>, U: IntoUrl>(
112 client_id: T,
113 authorization_code: T,
114 client_assertion: T,
115 redirect_uri: Option<U>,
116 ) -> IdentityResult<AuthorizationCodeCertificateCredential> {
117 let redirect_uri = {
118 if let Some(redirect_uri) = redirect_uri {
119 redirect_uri.into_url().ok()
120 } else {
121 None
122 }
123 };
124
125 Ok(AuthorizationCodeCertificateCredential {
126 app_config: AppConfig::builder(client_id.as_ref())
127 .redirect_uri_option(redirect_uri)
128 .build(),
129 authorization_code: Some(authorization_code.as_ref().to_owned()),
130 refresh_token: None,
131 code_verifier: None,
132 client_assertion_type: CLIENT_ASSERTION_TYPE.to_owned(),
133 client_assertion: client_assertion.as_ref().to_owned(),
134 token_cache: Default::default(),
135 })
136 }
137
138 #[cfg(feature = "openssl")]
139 pub fn builder(
140 client_id: impl AsRef<str>,
141 authorization_code: impl AsRef<str>,
142 x509: &X509Certificate,
143 ) -> IdentityResult<AuthorizationCodeCertificateCredentialBuilder> {
144 AuthorizationCodeCertificateCredentialBuilder::new_with_auth_code_and_x509(
145 authorization_code,
146 x509,
147 AppConfig::new(client_id.as_ref()),
148 )
149 }
150
151 pub fn authorization_url_builder(
152 client_id: impl TryInto<Uuid>,
153 ) -> AuthCodeAuthorizationUrlParameterBuilder {
154 AuthCodeAuthorizationUrlParameterBuilder::new(client_id)
155 }
156
157 fn execute_cached_token_refresh(&mut self, cache_id: String) -> AuthExecutionResult<Token> {
158 let response = self.execute()?;
159
160 if !response.status().is_success() {
161 return Err(AuthExecutionError::silent_token_auth(
162 response.into_http_response()?,
163 ));
164 }
165
166 let new_token: Token = response.json()?;
167 self.token_cache.store(cache_id, new_token.clone());
168
169 if new_token.refresh_token.is_some() {
170 self.refresh_token = new_token.refresh_token.clone();
171 }
172
173 Ok(new_token)
174 }
175
176 async fn execute_cached_token_refresh_async(
177 &mut self,
178 cache_id: String,
179 ) -> AuthExecutionResult<Token> {
180 let response = self.execute_async().await?;
181
182 if !response.status().is_success() {
183 return Err(AuthExecutionError::silent_token_auth(
184 response.into_http_response_async().await?,
185 ));
186 }
187
188 let new_token: Token = response.json().await?;
189
190 if new_token.refresh_token.is_some() {
191 self.refresh_token = new_token.refresh_token.clone();
192 }
193
194 self.token_cache.store(cache_id, new_token.clone());
195 Ok(new_token)
196 }
197}
198
199#[async_trait]
200impl TokenCache for AuthorizationCodeCertificateCredential {
201 type Token = Token;
202
203 fn get_token_silent(&mut self) -> Result<Self::Token, AuthExecutionError> {
204 let cache_id = self.app_config.cache_id.to_string();
205
206 match self.app_config.force_token_refresh {
207 ForceTokenRefresh::Never => {
208 if self.refresh_token.is_some() {
211 if let Ok(token) = self.execute_cached_token_refresh(cache_id.clone()) {
212 return Ok(token);
213 }
214 }
215
216 if let Some(token) = self.token_cache.get(cache_id.as_str()) {
217 if token.is_expired_sub(time::Duration::minutes(5)) {
218 if let Some(refresh_token) = token.refresh_token.as_ref() {
219 self.refresh_token = Some(refresh_token.to_owned());
220 }
221
222 self.execute_cached_token_refresh(cache_id)
223 } else {
224 Ok(token)
225 }
226 } else {
227 self.execute_cached_token_refresh(cache_id)
228 }
229 }
230 ForceTokenRefresh::Once | ForceTokenRefresh::Always => {
231 let token_result = self.execute_cached_token_refresh(cache_id);
232 if self.app_config.force_token_refresh == ForceTokenRefresh::Once {
233 self.app_config.force_token_refresh = ForceTokenRefresh::Never;
234 }
235 token_result
236 }
237 }
238 }
239
240 async fn get_token_silent_async(&mut self) -> Result<Self::Token, AuthExecutionError> {
241 let cache_id = self.app_config.cache_id.to_string();
242
243 match self.app_config.force_token_refresh {
244 ForceTokenRefresh::Never => {
245 if self.refresh_token.is_some() {
248 if let Ok(token) = self
249 .execute_cached_token_refresh_async(cache_id.clone())
250 .await
251 {
252 return Ok(token);
253 }
254 }
255
256 if let Some(old_token) = self.token_cache.get(cache_id.as_str()) {
257 if old_token.is_expired_sub(time::Duration::minutes(5)) {
258 if let Some(refresh_token) = old_token.refresh_token.as_ref() {
259 self.refresh_token = Some(refresh_token.to_owned());
260 }
261
262 self.execute_cached_token_refresh_async(cache_id).await
263 } else {
264 Ok(old_token.clone())
265 }
266 } else {
267 self.execute_cached_token_refresh_async(cache_id).await
268 }
269 }
270 ForceTokenRefresh::Once | ForceTokenRefresh::Always => {
271 let token_result = self.execute_cached_token_refresh_async(cache_id).await;
272 if self.app_config.force_token_refresh == ForceTokenRefresh::Once {
273 self.app_config.force_token_refresh = ForceTokenRefresh::Never;
274 }
275 token_result
276 }
277 }
278 }
279
280 fn with_force_token_refresh(&mut self, force_token_refresh: ForceTokenRefresh) {
281 self.app_config.force_token_refresh = force_token_refresh;
282 }
283}
284
285#[async_trait]
286impl TokenCredentialExecutor for AuthorizationCodeCertificateCredential {
287 fn form_urlencode(&mut self) -> IdentityResult<HashMap<String, String>> {
288 let mut serializer = AuthSerializer::new();
289 let client_id = self.app_config.client_id.to_string();
290 if client_id.is_empty() || self.app_config.client_id.is_nil() {
291 return AF::result(AuthParameter::ClientId);
292 }
293
294 if self.client_assertion.trim().is_empty() {
295 return AF::result(AuthParameter::ClientAssertion);
296 }
297
298 if self.client_assertion_type.trim().is_empty() {
299 self.client_assertion_type = CLIENT_ASSERTION_TYPE.to_owned();
300 }
301
302 serializer
303 .client_id(client_id.as_str())
304 .client_assertion(self.client_assertion.as_str())
305 .client_assertion_type(self.client_assertion_type.as_str())
306 .set_scope(self.app_config.scope.clone());
307
308 if let Some(redirect_uri) = self.app_config.redirect_uri.as_ref() {
309 serializer.redirect_uri(redirect_uri.as_str());
310 }
311
312 if let Some(code_verifier) = self.code_verifier.as_ref() {
313 serializer.code_verifier(code_verifier.as_ref());
314 }
315
316 if let Some(refresh_token) = self.refresh_token.as_ref() {
317 if refresh_token.trim().is_empty() {
318 return AF::msg_result(
319 AuthParameter::RefreshToken.alias(),
320 "refresh_token is empty - cannot be an empty string",
321 );
322 }
323
324 serializer
325 .refresh_token(refresh_token.as_ref())
326 .grant_type("refresh_token");
327
328 return serializer.as_credential_map(
329 vec![AuthParameter::Scope],
330 vec![
331 AuthParameter::RefreshToken,
332 AuthParameter::ClientId,
333 AuthParameter::GrantType,
334 AuthParameter::ClientAssertion,
335 AuthParameter::ClientAssertionType,
336 ],
337 );
338 } else if let Some(authorization_code) = self.authorization_code.as_ref() {
339 if authorization_code.trim().is_empty() {
340 return AF::msg_result(
341 AuthParameter::AuthorizationCode.alias(),
342 "authorization_code is empty - cannot be an empty string",
343 );
344 }
345
346 serializer
347 .authorization_code(authorization_code.as_str())
348 .grant_type("authorization_code");
349
350 return serializer.as_credential_map(
351 vec![AuthParameter::Scope, AuthParameter::CodeVerifier],
352 vec![
353 AuthParameter::AuthorizationCode,
354 AuthParameter::ClientId,
355 AuthParameter::GrantType,
356 AuthParameter::RedirectUri,
357 AuthParameter::ClientAssertion,
358 AuthParameter::ClientAssertionType,
359 ],
360 );
361 }
362
363 AF::msg_result(
364 format!(
365 "{} or {}",
366 AuthParameter::AuthorizationCode.alias(),
367 AuthParameter::RefreshToken.alias()
368 ),
369 "Either authorization code or refresh token is required",
370 )
371 }
372
373 fn client_id(&self) -> &Uuid {
374 &self.app_config.client_id
375 }
376
377 fn authority(&self) -> Authority {
378 self.app_config.authority.clone()
379 }
380
381 fn azure_cloud_instance(&self) -> AzureCloudInstance {
382 self.app_config.azure_cloud_instance
383 }
384
385 fn app_config(&self) -> &AppConfig {
386 &self.app_config
387 }
388}
389
390#[derive(Clone)]
391pub struct AuthorizationCodeCertificateCredentialBuilder {
392 credential: AuthorizationCodeCertificateCredential,
393}
394
395impl AuthorizationCodeCertificateCredentialBuilder {
396 #[cfg(feature = "openssl")]
397 pub(crate) fn new_with_auth_code_and_x509(
398 authorization_code: impl AsRef<str>,
399 x509: &X509Certificate,
400 app_config: AppConfig,
401 ) -> IdentityResult<AuthorizationCodeCertificateCredentialBuilder> {
402 let mut builder = Self {
403 credential: AuthorizationCodeCertificateCredential {
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: String::new(),
410 token_cache: Default::default(),
411 },
412 };
413
414 builder.with_x509(x509)?;
415 Ok(builder)
416 }
417
418 #[cfg(all(feature = "openssl", feature = "interactive-auth"))]
419 pub(crate) fn new_with_token(
420 token: Token,
421 x509: &X509Certificate,
422 app_config: AppConfig,
423 ) -> IdentityResult<AuthorizationCodeCertificateCredentialBuilder> {
424 let cache_id = app_config.cache_id.clone();
425 let mut token_cache = InMemoryCacheStore::new();
426 token_cache.store(cache_id, token);
427
428 let mut builder = Self {
429 credential: AuthorizationCodeCertificateCredential {
430 app_config,
431 authorization_code: None,
432 refresh_token: None,
433 code_verifier: None,
434 client_assertion_type: CLIENT_ASSERTION_TYPE.to_owned(),
435 client_assertion: String::new(),
436 token_cache,
437 },
438 };
439
440 builder.with_x509(x509)?;
441 Ok(builder)
442 }
443
444 pub fn with_authorization_code<T: AsRef<str>>(&mut self, authorization_code: T) -> &mut Self {
445 self.credential.authorization_code = Some(authorization_code.as_ref().to_owned());
446 self
447 }
448
449 pub fn with_refresh_token<T: AsRef<str>>(&mut self, refresh_token: T) -> &mut Self {
450 self.credential.authorization_code = None;
451 self.credential.refresh_token = Some(refresh_token.as_ref().to_owned());
452 self
453 }
454
455 pub fn with_redirect_uri(&mut self, redirect_uri: Url) -> &mut Self {
456 self.credential.app_config.redirect_uri = Some(redirect_uri);
457 self
458 }
459
460 pub fn with_code_verifier<T: AsRef<str>>(&mut self, code_verifier: T) -> &mut Self {
461 self.credential.code_verifier = Some(code_verifier.as_ref().to_owned());
462 self
463 }
464
465 #[cfg(feature = "openssl")]
466 pub fn with_x509(
467 &mut self,
468 certificate_assertion: &X509Certificate,
469 ) -> IdentityResult<&mut Self> {
470 if let Some(tenant_id) = self.credential.authority().tenant_id() {
471 self.with_client_assertion(
472 certificate_assertion.sign_with_tenant(Some(tenant_id.clone()))?,
473 );
474 } else {
475 self.with_client_assertion(certificate_assertion.sign_with_tenant(None)?);
476 }
477 Ok(self)
478 }
479
480 pub fn with_client_assertion<T: AsRef<str>>(&mut self, client_assertion: T) -> &mut Self {
481 self.credential.client_assertion = client_assertion.as_ref().to_owned();
482 self
483 }
484
485 pub fn with_client_assertion_type<T: AsRef<str>>(
486 &mut self,
487 client_assertion_type: T,
488 ) -> &mut Self {
489 self.credential.client_assertion_type = client_assertion_type.as_ref().to_owned();
490 self
491 }
492
493 pub fn credential(self) -> AuthorizationCodeCertificateCredential {
494 self.credential
495 }
496}
497
498impl From<AuthorizationCodeCertificateCredential>
499 for AuthorizationCodeCertificateCredentialBuilder
500{
501 fn from(credential: AuthorizationCodeCertificateCredential) -> Self {
502 AuthorizationCodeCertificateCredentialBuilder { credential }
503 }
504}
505
506impl From<AuthorizationCodeCertificateCredentialBuilder>
507 for AuthorizationCodeCertificateCredential
508{
509 fn from(builder: AuthorizationCodeCertificateCredentialBuilder) -> Self {
510 builder.credential
511 }
512}
513
514impl Debug for AuthorizationCodeCertificateCredentialBuilder {
515 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
516 self.credential.fmt(f)
517 }
518}