diff --git a/src/people.rs b/src/people.rs index a02a73b..fd5b5ea 100644 --- a/src/people.rs +++ b/src/people.rs @@ -31,6 +31,19 @@ pub struct Invited { pub created: bool, } +/// Whether a `GET /v1/person/` answer means the person exists. +/// Kanidm 1.11 answers a missing person with 200 and the body `null` +/// rather than 404, so the status alone would report everyone as +/// existing and no account would ever get created. `None` for an +/// answer that is neither. +fn person_lookup_found(status: u16, body: &str) -> Option { + match status { + 200 => Some(body.trim() != "null" && !body.trim().is_empty()), + 404 => Some(false), + _ => None, + } +} + pub struct Kanidm { base: String, token: String, @@ -60,12 +73,8 @@ impl Kanidm { } async fn person_exists(&self, username: &str) -> anyhow::Result { - let (status, _) = self.call(openidconnect::reqwest::Method::GET, &format!("/v1/person/{username}"), None).await?; - match status { - 200 => Ok(true), - 404 => Ok(false), - other => anyhow::bail!("kanidm: looking up {username}: http {other}"), - } + let (status, text) = self.call(openidconnect::reqwest::Method::GET, &format!("/v1/person/{username}"), None).await?; + person_lookup_found(status, &text).ok_or_else(|| anyhow::anyhow!("kanidm: looking up {username}: http {status}")) } /// The whole onboarding, in the order that leaves the least behind @@ -161,4 +170,13 @@ mod tests { assert_eq!(username_for("42@x.no"), "person"); assert_eq!(username_for("Ærlig@x.no"), "rlig"); } + + #[test] + fn a_null_person_answer_means_nobody_there() { + assert_eq!(person_lookup_found(200, "null"), Some(false)); + assert_eq!(person_lookup_found(200, "null\n"), Some(false)); + assert_eq!(person_lookup_found(200, r#"{"attrs":{"name":["bl"]}}"#), Some(true)); + assert_eq!(person_lookup_found(404, ""), Some(false)); + assert_eq!(person_lookup_found(401, "unauthorized"), None); + } }