mod authorization_policy;
pub(crate) use self::authorization_policy::AuthorizationPolicy;
use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
use async_lock::RwLock;
use azure_core::{
auth::{Secret, TokenCredential},
error::{ErrorKind, ResultExt},
Url,
};
use std::{
mem::replace,
ops::{Deref, DerefMut},
sync::Arc,
};
#[derive(Clone)]
pub struct StorageCredentials(pub Arc<RwLock<StorageCredentialsInner>>);
#[derive(Clone)]
pub enum StorageCredentialsInner {
Key(String, Secret),
SASToken(Vec<(String, String)>),
BearerToken(Secret),
TokenCredential(Arc<dyn TokenCredential>),
Anonymous,
}
impl StorageCredentials {
fn wrap(inner: StorageCredentialsInner) -> Self {
Self(Arc::new(RwLock::new(inner)))
}
pub fn access_key<A, K>(account: A, key: K) -> Self
where
A: Into<String>,
K: Into<Secret>,
{
Self::wrap(StorageCredentialsInner::Key(account.into(), key.into()))
}
pub fn sas_token<S>(token: S) -> azure_core::Result<Self>
where
S: AsRef<str>,
{
let params = get_sas_token_parms(token.as_ref())?;
Ok(Self::wrap(StorageCredentialsInner::SASToken(params)))
}
pub fn bearer_token<T>(token: T) -> Self
where
T: Into<Secret>,
{
Self::wrap(StorageCredentialsInner::BearerToken(token.into()))
}
pub fn token_credential(credential: Arc<dyn TokenCredential>) -> Self {
Self::wrap(StorageCredentialsInner::TokenCredential(credential))
}
pub fn anonymous() -> Self {
Self::wrap(StorageCredentialsInner::Anonymous)
}
pub fn emulator() -> Self {
Self::access_key(EMULATOR_ACCOUNT, Secret::new(EMULATOR_ACCOUNT_KEY))
}
pub async fn replace(&self, other: Self) -> azure_core::Result<()> {
if Arc::ptr_eq(&self.0, &other.0) {
return Ok(());
}
let mut creds = self.0.write().await;
let other = other.0.write().await;
let creds = creds.deref_mut();
let other = other.deref().clone();
let _old_creds = replace(creds, other);
Ok(())
}
}
impl std::fmt::Debug for StorageCredentials {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let creds = self.0.try_read();
match creds.as_deref() {
None => f
.debug_struct("StorageCredentials")
.field("credential", &"locked")
.finish(),
Some(inner) => match &inner {
StorageCredentialsInner::Key(_, _) => f
.debug_struct("StorageCredentials")
.field("credential", &"Key")
.finish(),
StorageCredentialsInner::SASToken(_) => f
.debug_struct("StorageCredentials")
.field("credential", &"SASToken")
.finish(),
StorageCredentialsInner::BearerToken(_) => f
.debug_struct("StorageCredentials")
.field("credential", &"BearerToken")
.finish(),
StorageCredentialsInner::TokenCredential(_) => f
.debug_struct("StorageCredentials")
.field("credential", &"TokenCredential")
.finish(),
StorageCredentialsInner::Anonymous => f
.debug_struct("StorageCredentials")
.field("credential", &"Anonymous")
.finish(),
},
}
}
}
impl From<Arc<dyn TokenCredential>> for StorageCredentials {
fn from(cred: Arc<dyn TokenCredential>) -> Self {
Self::token_credential(cred)
}
}
impl TryFrom<&Url> for StorageCredentials {
type Error = azure_core::Error;
fn try_from(value: &Url) -> Result<Self, Self::Error> {
match value.query() {
Some(query) => Self::sas_token(query),
None => Ok(Self::anonymous()),
}
}
}
fn get_sas_token_parms(sas_token: &str) -> azure_core::Result<Vec<(String, String)>> {
let base_url = Url::parse("https://blob.core.windows.net").unwrap();
let url = Url::options().base_url(Some(&base_url));
let url = if sas_token.starts_with('?') {
url.parse(sas_token)
} else {
url.parse(&format!("?{sas_token}"))
}
.with_context(ErrorKind::DataConversion, || {
format!("failed to parse SAS token: {sas_token}")
})?;
Ok(url
.query_pairs()
.map(|p| (String::from(p.0), String::from(p.1)))
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_replacement() -> azure_core::Result<()> {
let base = StorageCredentials::anonymous();
let other = StorageCredentials::bearer_token(Secret::new("foo"));
base.replace(other).await?;
{
let inner = base.0.read().await;
let inner_locked = inner.deref();
assert!(
matches!(&inner_locked, &StorageCredentialsInner::BearerToken(value) if value.secret() == "foo")
);
}
base.replace(base.clone()).await?;
Ok(())
}
}