use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use url::Url;
use crate::credentials::{CredentialSource, Format};
use crate::token_source::default_http_client;
use crate::token_source::external_account_source::error::Error;
use crate::token_source::external_account_source::subject_token_source::SubjectTokenSource;
pub struct UrlSubjectTokenSource {
url: Url,
headers: HashMap<String, String>,
format: Format,
}
impl UrlSubjectTokenSource {
pub async fn new(value: CredentialSource) -> Result<Self, Error> {
let url = value.url.ok_or(Error::MissingTokenURL)?;
let url = Url::parse(&url).map_err(Error::URLError)?;
let headers = value.headers.ok_or(Error::MissingHeaders)?;
let format = value.format.ok_or(Error::MissingFormat)?;
Ok(Self { url, headers, format })
}
async fn create_subject_token(&self) -> Result<String, Error> {
let client = default_http_client();
let mut request = client.get(self.url.clone());
for (key, val) in &self.headers {
request = request.header(key, val);
}
let response = request.send().await.map_err(Error::HttpError)?;
if !response.status().is_success() {
return Err(Error::UnexpectedStatusOnGetSessionToken(response.status().as_u16()));
}
let body = response.text_with_charset("utf-8").await?;
let body = body.chars().take(1 << 20).collect::<String>();
let format_type = self.format.tp.as_str();
match format_type {
"json" => {
let data: Value = serde_json::from_str(&body).map_err(Error::JsonError)?;
if let Some(token) = data[&self.format.subject_token_field_name].as_str() {
Ok(token.to_string())
} else {
Err(Error::MissingSubjectTokenFieldName)
}
}
"text" | "" => Ok(body),
_ => Err(Error::UnsupportedFormatType),
}
}
}
#[async_trait]
impl SubjectTokenSource for UrlSubjectTokenSource {
async fn subject_token(&self) -> Result<String, Error> {
self.create_subject_token().await
}
}