use azure_core::headers::{HeaderName, Headers};
use azure_core::{Context, Policy, PolicyResult, Request};
use azure_storage::{core::storage_shared_key_credential::StorageSharedKeyCredential, hmac::sign};
use http::{HeaderValue, Method};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SharedKeyAuthorizationPolicy {
credential: StorageSharedKeyCredential,
}
impl SharedKeyAuthorizationPolicy {
pub(crate) fn new(credential: StorageSharedKeyCredential) -> Self {
Self { credential }
}
}
#[async_trait::async_trait]
impl Policy for SharedKeyAuthorizationPolicy {
async fn send(
&self,
ctx: &Context,
request: &mut Request,
next: &[Arc<dyn Policy>],
) -> PolicyResult {
assert!(
!next.is_empty(),
"Authorization policies cannot be the last policy of a pipeline"
);
let headers_mut = request.headers_mut();
headers_mut.insert(
azure_core::headers::MS_DATE,
HeaderValue::from_str(
format!("{}", chrono::Utc::now().format("%a, %d %h %Y %T GMT")).as_str(),
)?,
);
headers_mut.insert(
azure_core::headers::VERSION,
HeaderValue::from_str("2019-12-12")?,
);
let url = url::Url::parse(&request.uri().to_string()).unwrap();
let auth = generate_authorization(
request.headers(),
&url,
&request.method(),
&self.credential.account_name,
&self.credential.account_key,
);
request
.headers_mut()
.insert(http::header::AUTHORIZATION, HeaderValue::from_str(&auth)?);
next[0].send(ctx, request, &next[1..]).await
}
}
fn generate_authorization(
http_headers: &Headers,
url: &url::Url,
http_method: &Method,
storage_account_name: &str,
shared_key: &str,
) -> String {
let str_to_sign = string_to_sign(http_headers, url, http_method, storage_account_name);
let auth = sign(&str_to_sign, shared_key).unwrap();
format!("SharedKey {}:{}", storage_account_name, auth)
}
#[allow(unknown_lints)]
fn string_to_sign(
http_headers: &Headers,
url: &url::Url,
http_method: &Method,
storage_account_name: &str,
) -> String {
let cl = http_headers
.get(&http::header::CONTENT_LENGTH.into())
.map(|s| if s.as_str() == "0" { "" } else { s.as_str() })
.unwrap_or("");
format!(
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}{}",
http_method.as_str(),
add_if_exists(http_headers, &http::header::CONTENT_ENCODING.into()),
add_if_exists(http_headers, &http::header::CONTENT_LANGUAGE.into()),
cl,
add_if_exists(http_headers, &azure_storage::headers::CONTENT_MD5.into()),
add_if_exists(http_headers, &http::header::CONTENT_TYPE.into()),
add_if_exists(http_headers, &http::header::DATE.into()),
add_if_exists(http_headers, &http::header::IF_MODIFIED_SINCE.into()),
add_if_exists(http_headers, &http::header::IF_MATCH.into()),
add_if_exists(http_headers, &http::header::IF_NONE_MATCH.into()),
add_if_exists(http_headers, &http::header::IF_UNMODIFIED_SINCE.into()),
add_if_exists(http_headers, &http::header::RANGE.into()),
canonicalize_header(http_headers),
canonicalized_resource(storage_account_name, url)
)
}
fn add_if_exists<'a>(h: &'a Headers, key: &HeaderName) -> &'a str {
match h.get(key) {
Some(ce) => ce.as_str(),
None => "",
}
}
fn canonicalize_header(h: &Headers) -> String {
let mut v_headers = h
.iter()
.filter(|(k, _v)| k.as_str().starts_with("x-ms"))
.map(|(k, _)| k)
.collect::<Vec<_>>();
v_headers.sort_unstable();
let mut can = String::new();
for header_name in v_headers {
let s = h.get(header_name).unwrap().as_str();
can = can + header_name.as_str() + ":" + s + "\n";
}
can
}
fn canonicalized_resource(account: &str, u: &url::Url) -> String {
let mut can_res: String = String::new();
can_res += "/";
can_res += account;
let paths = u.path_segments().unwrap();
for p in paths {
can_res.push('/');
can_res.push_str(&*p);
}
can_res += "\n";
let query_pairs = u.query_pairs(); {
let mut qps = Vec::new();
{
for (q, _p) in query_pairs {
trace!("adding to qps {:?}", q);
if !(qps.iter().any(|x: &String| x == q.as_ref())) {
qps.push(q.into_owned());
}
}
}
qps.sort();
for qparam in qps {
let ret = lexy_sort(&query_pairs, &qparam);
can_res = can_res + &qparam.to_lowercase() + ":";
for (i, item) in ret.iter().enumerate() {
if i > 0 {
can_res += ","
}
can_res += item;
}
can_res += "\n";
}
};
can_res[0..can_res.len() - 1].to_owned()
}
fn lexy_sort<'a>(
vec: &'a url::form_urlencoded::Parse,
query_param: &str,
) -> Vec<std::borrow::Cow<'a, str>> {
let mut v_values = Vec::new();
for item in vec.filter(|x| x.0 == *query_param) {
v_values.push(item.1)
}
v_values.sort();
v_values
}