src/first_import.rs
//! **The admin's first-import authorization** (lanes HOL-CDS, HOL-NCSC): holger's
//! `POST /-/bundle/first-import?vendor=&product=&sequence=`, as the Admin → Sync pane asks it.
//! A Holger with no anti-rollback floor refuses every bundle until an admin on the secure side
//! names the one that sets it (`holger-import-9`). The console builds the door's path and says
//! holger's answer; holger checks the admin, audits and keeps the grant (used once).
fn query_safe(v: &str) -> String {
v.chars().map(|c| if c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-') { c.to_string() } else { c.to_string().bytes().map(|b| format!("%{b:02X}")).collect() }).collect()
}
/// Pure: the door for `vendor`, `product` at `sequence` (as typed), or why the form is not
/// one yet. holger judges the rest (an admin, a floor that does not stand yet).
pub fn path(vendor: &str, product: &str, sequence: &str) -> Result<String, String> {
let (v, p, s) = (vendor.trim(), product.trim(), sequence.trim());
if v.is_empty() || p.is_empty() {
return Err("name the bundle's vendor and product".to_string());
}
let n: u64 = s.parse().map_err(|_| format!("the sequence is a whole number, not {s:?}"))?;
if n == 0 {
return Err("the sequence starts at 1".to_string());
}
Ok(format!("/-/bundle/first-import?vendor={}&product={}&sequence={n}", query_safe(v), query_safe(p)))
}
/// Pure: what the page says once holger answered: `Ok` with holger's record of the grant, or
/// `Err` with holger's refusal verbatim and its status. The console adds no judgement.
pub fn granted(status: u16, body: &str) -> Result<String, String> {
if status != 200 {
let why = if body.trim().is_empty() { "holger gave no reason" } else { body.trim() };
return Err(format!("holger refused the authorization ({status}): {why}"));
}
let v: serde_json::Value = serde_json::from_str(body).map_err(|e| format!("holger answered something unreadable: {e}"))?;
Ok(format!(
"{} {} sequence {} may set the floor once (authorized by {})",
v["vendor"].as_str().unwrap_or("?"),
v["product"].as_str().unwrap_or("?"),
v["sequence"].as_u64().unwrap_or(0),
v["by"].as_str().unwrap_or("?")
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_form_names_holgers_door_or_says_what_is_missing() {
assert_eq!(path(" online ", "pypi", "7").unwrap(), "/-/bundle/first-import?vendor=online&product=pypi&sequence=7");
assert_eq!(path("a b", "p&q", "1").unwrap(), "/-/bundle/first-import?vendor=a%20b&product=p%26q&sequence=1");
assert!(path("", "pypi", "1").unwrap_err().contains("vendor and product"));
assert!(path("v", "p", "x").unwrap_err().contains("whole number"));
assert!(path("v", "p", "0").unwrap_err().contains("starts at 1"));
}
#[test]
fn holgers_answer_is_said_as_it_came() {
let ok = granted(200, r#"{"vendor":"online","product":"pypi","sequence":7,"by":"anna","at":1}"#).unwrap();
assert_eq!(ok, "online pypi sequence 7 may set the floor once (authorized by anna)");
let refused = granted(403, "[holger-import-11] not an admin").unwrap_err();
assert!(refused.contains("403") && refused.contains("[holger-import-11]"), "{refused}");
assert!(granted(500, "").unwrap_err().contains("no reason"));
}
}