From f36d88a69ccd887f0e2bbc73fad673d8b084e7d7 Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Wed, 23 Sep 2026 14:33:25 +0200 Subject: [PATCH] people: a missing person is 200 + null in Kanidm 1.11, not 404 The existence check went by status alone, so every invitee looked like an existing account: creation was skipped and the group step failed. Read the body; `null` means nobody there. Co-Authored-By: Claude Fable 5.1 --- src/people.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) 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); + } }