use actix_web::http::header::{HeaderMap, HeaderName};
use myc_http_tools::settings::MYCELIUM_HEADER_PREFIX;
#[tracing::instrument(name = "strip_inbound_mycelium_headers", skip_all)]
pub(super) fn strip_inbound_mycelium_headers(headers: &mut HeaderMap) {
let inbound_mycelium_headers = headers
.keys()
.filter(|name| name.as_str().starts_with(MYCELIUM_HEADER_PREFIX))
.cloned()
.collect::<Vec<HeaderName>>();
for header_name in inbound_mycelium_headers {
tracing::debug!(
stage = "router.strip_inbound_mycelium_headers",
header = header_name.as_str(),
"Client-supplied Mycelium header removed"
);
headers.remove(&header_name);
}
}
#[cfg(test)]
mod tests {
use super::strip_inbound_mycelium_headers;
use actix_web::http::header::{HeaderMap, HeaderName, HeaderValue};
use myc_http_tools::settings::{
DEFAULT_CONNECTION_STRING_KEY, DEFAULT_EMAIL_KEY,
DEFAULT_MYCELIUM_ROLE_KEY, DEFAULT_PROFILE_KEY, DEFAULT_SCOPE_KEY,
DEFAULT_TENANT_ID_KEY,
};
fn headers_from(pairs: &[(&str, &str)]) -> HeaderMap {
let mut headers = HeaderMap::new();
for (name, value) in pairs {
headers.append(
HeaderName::from_bytes(name.as_bytes()).unwrap(),
HeaderValue::from_str(value).unwrap(),
);
}
headers
}
#[test]
fn forged_profile_is_removed() {
let mut headers =
headers_from(&[(DEFAULT_PROFILE_KEY, "forged-profile")]);
strip_inbound_mycelium_headers(&mut headers);
assert!(headers.get(DEFAULT_PROFILE_KEY).is_none());
}
#[test]
fn headers_without_a_producer_are_removed() {
let mut headers = headers_from(&[
(DEFAULT_SCOPE_KEY, "forged-scope"),
(DEFAULT_MYCELIUM_ROLE_KEY, "forged-role"),
(DEFAULT_TENANT_ID_KEY, "forged-tenant"),
(DEFAULT_EMAIL_KEY, "forged@email.com"),
]);
strip_inbound_mycelium_headers(&mut headers);
assert!(headers.get(DEFAULT_SCOPE_KEY).is_none());
assert!(headers.get(DEFAULT_MYCELIUM_ROLE_KEY).is_none());
assert!(headers.get(DEFAULT_TENANT_ID_KEY).is_none());
assert!(headers.get(DEFAULT_EMAIL_KEY).is_none());
}
#[test]
fn connection_string_is_not_forwarded_downstream() {
let mut headers =
headers_from(&[(DEFAULT_CONNECTION_STRING_KEY, "user-credential")]);
strip_inbound_mycelium_headers(&mut headers);
assert!(headers.get(DEFAULT_CONNECTION_STRING_KEY).is_none());
}
#[test]
fn non_mycelium_headers_are_untouched() {
let mut headers = headers_from(&[
("authorization", "Bearer token"),
("content-type", "application/json"),
("x-crab-shell-session", "session-42"),
]);
strip_inbound_mycelium_headers(&mut headers);
assert_eq!(headers.get("authorization").unwrap(), "Bearer token");
assert_eq!(headers.get("content-type").unwrap(), "application/json");
assert_eq!(headers.get("x-crab-shell-session").unwrap(), "session-42");
}
#[test]
fn matching_is_prefix_based_and_case_insensitive() {
let mut headers = headers_from(&[
("X-Mycelium-Profile", "forged-profile"),
("x-mycelium", "not-namespaced"),
("x-my-header", "unrelated"),
]);
strip_inbound_mycelium_headers(&mut headers);
assert!(headers.get("x-mycelium-profile").is_none());
assert_eq!(headers.get("x-mycelium").unwrap(), "not-namespaced");
assert_eq!(headers.get("x-my-header").unwrap(), "unrelated");
}
#[test]
fn multi_valued_headers_keep_every_value() {
let mut headers = headers_from(&[
("accept", "text/html"),
("accept", "application/json"),
(DEFAULT_PROFILE_KEY, "forged-profile"),
]);
strip_inbound_mycelium_headers(&mut headers);
let accepted = headers
.get_all("accept")
.map(|value| value.to_str().unwrap().to_owned())
.collect::<Vec<String>>();
assert_eq!(accepted, vec!["text/html", "application/json"]);
assert!(headers.get(DEFAULT_PROFILE_KEY).is_none());
}
}