From 4e2bde6da33b9175e6891b15bdc07365802e49c7 Mon Sep 17 00:00:00 2001 From: tom27052006 <83423411+tom27052006@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:11:09 +0200 Subject: [PATCH] Normalize SSO default organization UUID before lookup Uuid::parse_str also accepts uppercase, braced and non-hyphenated forms, but stored organization uuids are lowercase hyphenated and compared as strings. A non-canonical SSO_DEFAULT_ORGANIZATION_UUID passed validation but failed every first SSO sign-in with "organization does not exist". Normalize the value to the canonical form before the database lookup. --- src/sso.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/sso.rs b/src/sso.rs index d254284c..ad849365 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -363,7 +363,7 @@ async fn enroll_user_in_default_organization(user: &User, conn: &DbConn) -> ApiR let Some(org_uuid) = CONFIG.sso_default_organization_uuid() else { return Ok(()); }; - let org_id = OrganizationId::from(org_uuid); + let org_id = normalize_organization_uuid(&org_uuid)?; if Membership::find_by_user_and_org(&user.uuid, &org_id, conn).await.is_some() { return Ok(()); @@ -380,6 +380,15 @@ async fn enroll_user_in_default_organization(user: &User, conn: &DbConn) -> ApiR Ok(()) } +// `Uuid::parse_str` also accepts non-canonical forms (uppercase, braced, without hyphens), +// while stored organization uuids are always lowercase hyphenated and compared as strings. +fn normalize_organization_uuid(org_uuid: &str) -> ApiResult { + let Ok(parsed) = uuid::Uuid::parse_str(org_uuid) else { + err!("`SSO_DEFAULT_ORGANIZATION_UUID` must be a valid UUID") + }; + Ok(OrganizationId::from(parsed.to_string())) +} + fn new_default_sso_membership(user_uuid: UserId, org_id: OrganizationId) -> Membership { let mut membership = Membership::new(user_uuid, org_id, None); membership.status = MembershipStatus::Accepted as i32; @@ -405,6 +414,23 @@ mod default_organization_tests { assert_eq!(membership.atype, MembershipType::User as i32); assert!(membership.invited_by_email.is_none()); } + + #[test] + fn normalizes_organization_uuid_to_canonical_form() { + for input in [ + "1B2C3D4E-5F60-7182-93A4-B5C6D7E8F901", + "{1b2c3d4e-5f60-7182-93a4-b5c6d7e8f901}", + "1b2c3d4e5f60718293a4b5c6d7e8f901", + ] { + let org_id = normalize_organization_uuid(input).expect("valid UUID form should be accepted"); + assert_eq!(org_id.to_string(), "1b2c3d4e-5f60-7182-93a4-b5c6d7e8f901"); + } + } + + #[test] + fn rejects_invalid_organization_uuid() { + assert!(normalize_organization_uuid("not-a-uuid").is_err()); + } } // We always return a refresh_token (with no refresh_token some secrets are not displayed in the web front).