azure_identity/token_credentials/
client_secret_credentials.rs
use crate::token_credentials::cache::TokenCache;
use crate::{oauth2_http_client::Oauth2HttpClient, TokenCredentialOptions};
use azure_core::Error;
use azure_core::{
auth::{AccessToken, Secret, TokenCredential},
error::{ErrorKind, ResultExt},
HttpClient, Url,
};
use oauth2::{basic::BasicClient, AuthType, AuthUrl, Scope, TokenUrl};
use std::{str, sync::Arc};
use time::OffsetDateTime;
const AZURE_TENANT_ID_ENV_KEY: &str = "AZURE_TENANT_ID";
const AZURE_CLIENT_ID_ENV_KEY: &str = "AZURE_CLIENT_ID";
const AZURE_CLIENT_SECRET_ENV_KEY: &str = "AZURE_CLIENT_SECRET";
pub mod tenant_ids {
pub const TENANT_ID_COMMON: &str = "common";
pub const TENANT_ID_ADFS: &str = "adfs";
}
#[derive(Debug)]
pub struct ClientSecretCredential {
http_client: Arc<dyn HttpClient>,
authority_host: Url,
tenant_id: String,
client_id: oauth2::ClientId,
client_secret: Option<oauth2::ClientSecret>,
cache: TokenCache,
}
impl ClientSecretCredential {
pub fn new(
http_client: Arc<dyn HttpClient>,
authority_host: Url,
tenant_id: String,
client_id: String,
client_secret: String,
) -> ClientSecretCredential {
ClientSecretCredential {
http_client,
authority_host,
tenant_id,
client_id: oauth2::ClientId::new(client_id),
client_secret: Some(oauth2::ClientSecret::new(client_secret)),
cache: TokenCache::new(),
}
}
async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
let mut token_url = self.authority_host.clone();
token_url
.path_segments_mut()
.map_err(|_| {
Error::with_message(ErrorKind::Credential, || {
format!("invalid authority host {}", self.authority_host)
})
})?
.extend(&[&self.tenant_id, "oauth2", "v2.0", "token"]);
let mut auth_url = self.authority_host.clone();
auth_url
.path_segments_mut()
.map_err(|_| {
Error::with_message(ErrorKind::Credential, || {
format!("invalid authority host {}", self.authority_host)
})
})?
.extend(&[&self.tenant_id, "oauth2", "v2.0", "authorize"]);
let client = BasicClient::new(
self.client_id.clone(),
self.client_secret.clone(),
AuthUrl::from_url(auth_url),
Some(TokenUrl::from_url(token_url)),
)
.set_auth_type(AuthType::RequestBody);
let scopes = scopes.iter().map(ToString::to_string).map(Scope::new);
let oauth_http_client = Oauth2HttpClient::new(self.http_client.clone());
let token_result = client
.exchange_client_credentials()
.add_scopes(scopes)
.request_async(|request| oauth_http_client.request(request))
.await
.map(|r| {
use oauth2::TokenResponse as _;
AccessToken::new(
Secret::new(r.access_token().secret().to_owned()),
OffsetDateTime::now_utc() + r.expires_in().unwrap_or_default(),
)
})
.context(ErrorKind::Credential, "request token error")?;
Ok(token_result)
}
pub fn create(
options: impl Into<TokenCredentialOptions>,
) -> azure_core::Result<ClientSecretCredential> {
let options = options.into();
let http_client = options.http_client();
let authority_host = options.authority_host()?;
let env = options.env();
let tenant_id =
env.var(AZURE_TENANT_ID_ENV_KEY)
.with_context(ErrorKind::Credential, || {
format!(
"client secret credential requires {} environment variable",
AZURE_TENANT_ID_ENV_KEY
)
})?;
let client_id =
env.var(AZURE_CLIENT_ID_ENV_KEY)
.with_context(ErrorKind::Credential, || {
format!(
"client secret credential requires {} environment variable",
AZURE_CLIENT_ID_ENV_KEY
)
})?;
let client_secret =
env.var(AZURE_CLIENT_SECRET_ENV_KEY)
.with_context(ErrorKind::Credential, || {
format!(
"client secret credential requires {} environment variable",
AZURE_CLIENT_SECRET_ENV_KEY
)
})?;
Ok(ClientSecretCredential::new(
http_client,
authority_host,
tenant_id,
client_id,
client_secret,
))
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl TokenCredential for ClientSecretCredential {
async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
self.cache.get_token(scopes, self.get_token(scopes)).await
}
async fn clear_cache(&self) -> azure_core::Result<()> {
self.cache.clear().await
}
}