use azure_core::{Context, Policy, PolicyResult, Request, Response};
use azure_storage::core::storage_shared_key_credential::StorageSharedKeyCredential;
use http::{HeaderMap, HeaderValue, Method};
use ring::hmac;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SharedKeyAuthorizationPolicy {
base_url: String,
credential: StorageSharedKeyCredential,
}
impl SharedKeyAuthorizationPolicy {
pub(crate) fn new(base_url: String, credential: StorageSharedKeyCredential) -> Self {
Self {
base_url,
credential,
}
}
}
#[async_trait::async_trait]
impl Policy for SharedKeyAuthorizationPolicy {
async fn send(
&self,
ctx: &Context,
request: &mut Request,
next: &[Arc<dyn Policy>],
) -> PolicyResult<Response> {
if next.is_empty() {
return Err(Box::new(azure_core::PipelineError::InvalidTailPolicy(
"Authorization policies cannot be the last policy of a pipeline".to_owned(),
)));
}
let headers_mut = request.headers_mut();
headers_mut.append(
azure_core::headers::MS_DATE,
HeaderValue::from_str(
format!("{}", chrono::Utc::now().format("%a, %d %h %Y %T GMT")).as_str(),
)?,
);
headers_mut.append(
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()
.append(http::header::AUTHORIZATION, HeaderValue::from_str(&auth)?);
next[0].send(ctx, request, &next[1..]).await
}
}
fn generate_authorization(
http_headers: &HeaderMap,
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 = encode_str_to_sign(&str_to_sign, shared_key);
format!("SharedKey {}:{}", storage_account_name, auth)
}
#[allow(unknown_lints)]
fn string_to_sign(
http_headers: &HeaderMap,
url: &url::Url,
http_method: &Method,
storage_account_name: &str,
) -> String {
let cl = http_headers
.get(http::header::CONTENT_LENGTH)
.map(|s| if s == "0" { "" } else { s.to_str().unwrap() })
.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),
add_if_exists(http_headers, http::header::CONTENT_LANGUAGE),
cl,
add_if_exists(http_headers, azure_storage::headers::CONTENT_MD5),
add_if_exists(http_headers, http::header::CONTENT_TYPE),
add_if_exists(http_headers, http::header::DATE),
add_if_exists(http_headers, http::header::IF_MODIFIED_SINCE),
add_if_exists(http_headers, http::header::IF_MATCH),
add_if_exists(http_headers, http::header::IF_NONE_MATCH),
add_if_exists(http_headers, http::header::IF_UNMODIFIED_SINCE),
add_if_exists(http_headers, http::header::RANGE),
canonicalize_header(http_headers),
canonicalized_resource(storage_account_name, url)
)
}
fn add_if_exists<K: http::header::AsHeaderName>(h: &HeaderMap, key: K) -> &str {
match h.get(key) {
Some(ce) => ce.to_str().unwrap(),
None => "",
}
}
fn canonicalize_header(h: &HeaderMap) -> String {
let mut v_headers = h
.iter()
.filter(|(k, _v)| k.as_str().starts_with("x-ms"))
.map(|(k, _)| k.as_str())
.collect::<Vec<_>>();
v_headers.sort_unstable();
let mut can = String::new();
for header_name in v_headers {
let s = h.get(header_name).unwrap().to_str().unwrap();
can = can + header_name + ":" + 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
}
fn encode_str_to_sign(str_to_sign: &str, hmac_key: &str) -> String {
let key = hmac::Key::new(ring::hmac::HMAC_SHA256, &base64::decode(hmac_key).unwrap());
let sig = hmac::sign(&key, str_to_sign.as_bytes());
base64::encode(sig.as_ref())
}