graph-oauth 3.0.0

Rust SDK Client for Microsoft Identity Platform
use crate::identity::credentials::app_config::AppConfig;
use crate::identity::{
    tracing_targets::CREDENTIAL_EXECUTOR, Authority, AuthorizationRequestParts, AzureCloudInstance,
};
use async_trait::async_trait;
use dyn_clone::DynClone;
use graph_error::AuthExecutionError;
use graph_error::{AuthExecutionResult, IdentityResult};
use graph_http::api_impl::{GraphClientConfiguration, MinimalAsyncClient, MinimalBlockingClient};
use reqwest::header::HeaderMap;
use std::collections::HashMap;
use std::fmt::Debug;
use tower::{Service, ServiceExt};
use url::{ParseError, Url};
use uuid::Uuid;

dyn_clone::clone_trait_object!(TokenCredentialExecutor);

#[async_trait]
pub trait TokenCredentialExecutor: DynClone + Debug {
    fn uri(&mut self) -> IdentityResult<Url> {
        Ok(self.azure_cloud_instance().token_uri(&self.authority())?)
    }

    fn form_urlencode(&mut self) -> IdentityResult<HashMap<String, String>>;

    fn request_parts(&mut self) -> IdentityResult<AuthorizationRequestParts> {
        let uri = self.uri()?;
        let form = self.form_urlencode()?;
        let basic_auth = self.basic_auth();
        let extra_headers = self.extra_header_parameters();
        let extra_query_params = self.extra_query_parameters();

        let mut auth_request = AuthorizationRequestParts::new(uri, form, basic_auth);
        auth_request.with_extra_headers(extra_headers);
        auth_request.with_extra_query_parameters(extra_query_params);

        Ok(auth_request)
    }

    fn build_request(
        &mut self,
    ) -> AuthExecutionResult<(reqwest::blocking::RequestBuilder, MinimalBlockingClient)> {
        let http_client = MinimalBlockingClient::from(self.config().clone());
        let auth_request = self.request_parts()?;
        let basic_auth = auth_request.basic_auth;

        if let Some((client_identifier, secret)) = basic_auth {
            let request_builder = http_client
                .inner
                .post(auth_request.uri)
                .basic_auth(client_identifier, Some(secret))
                .headers(auth_request.headers)
                .form(&auth_request.form_urlencoded);

            tracing::debug!(
                 target: CREDENTIAL_EXECUTOR,
                "authorization request constructed"
            );
            Ok((request_builder, http_client))
        } else {
            let request_builder = http_client
                .inner
                .post(auth_request.uri)
                .headers(auth_request.headers)
                .form(&auth_request.form_urlencoded);

            tracing::debug!(
                 target: CREDENTIAL_EXECUTOR,
                "authorization request constructed"
            );
            Ok((request_builder, http_client))
        }
    }

    async fn build_request_async(
        &mut self,
    ) -> AuthExecutionResult<(reqwest::RequestBuilder, MinimalAsyncClient)> {
        let http_client = MinimalAsyncClient::from(self.config().clone());
        let auth_request = self.request_parts()?;
        let basic_auth = auth_request.basic_auth;

        if let Some((client_identifier, secret)) = basic_auth {
            let request_builder = http_client
                .inner
                .post(auth_request.uri)
                .basic_auth(client_identifier, Some(secret))
                .headers(auth_request.headers)
                .form(&auth_request.form_urlencoded);

            tracing::debug!(
                target: CREDENTIAL_EXECUTOR,
                "authorization request constructed"
            );
            Ok((request_builder, http_client))
        } else {
            let request_builder = http_client
                .inner
                .post(auth_request.uri)
                .headers(auth_request.headers)
                .form(&auth_request.form_urlencoded);

            tracing::debug!(
                target: CREDENTIAL_EXECUTOR,
                "authorization request constructed"
            );
            Ok((request_builder, http_client))
        }
    }

    fn client_id(&self) -> &Uuid {
        &self.app_config().client_id
    }

    fn authority(&self) -> Authority {
        self.app_config().authority.clone()
    }

    fn azure_cloud_instance(&self) -> AzureCloudInstance {
        self.app_config().azure_cloud_instance
    }

    fn basic_auth(&self) -> Option<(String, String)> {
        None
    }

    fn app_config(&self) -> &AppConfig;

    fn extra_header_parameters(&self) -> &HeaderMap {
        &self.app_config().extra_header_parameters
    }

    fn issuer(&self) -> Result<Url, ParseError> {
        self.azure_cloud_instance().issuer(&self.authority())
    }

    fn extra_query_parameters(&self) -> &HashMap<String, String> {
        &self.app_config().extra_query_parameters
    }

    fn config(&self) -> &GraphClientConfiguration {
        &self.app_config().config
    }

    fn execute(&mut self) -> AuthExecutionResult<reqwest::blocking::Response> {
        let (request_builder, minimal_blocking_client) = self.build_request()?;
        let request = request_builder.build()?;
        let response = minimal_blocking_client.inner.execute(request)?;
        let status = response.status();
        tracing::debug!(target: CREDENTIAL_EXECUTOR, "authorization response received; status={status:#?}");
        Ok(response)
    }

    async fn execute_async(&mut self) -> AuthExecutionResult<reqwest::Response> {
        let (request_builder, mut minimal_async_client) = self.build_request_async().await?;
        let request = request_builder.build()?;
        let response = minimal_async_client
            .service
            .ready()
            .await
            .map_err(AuthExecutionError::from)?
            .call(request)
            .await
            .map_err(AuthExecutionError::from)?;
        let status = response.status();
        tracing::debug!(target: CREDENTIAL_EXECUTOR, "authorization response received; status={status:#?}");
        Ok(response)
    }
}