Skip to content

[Detail Bug] SAML SSO: Azure AD tenant GUID validation rejects uppercase/mixed-case GUIDs #34464

Closed

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_3377c26d-da48-4ccd-b83a-22c542f4fe83/bugs/bug_780eff9a-ec44-486b-8ea6-b73e12364c74

Introduced in #21839 by @shrushti2000 on Sep 23, 2025

Summary

  • Context: SamlValidator validates admin-entered SAML SSO configuration before it is persisted; validateAzureAdTenant is invoked for Azure AD entity IDs to confirm the configured tenant is valid.
  • Bug: The tenant-ID format check uses a regex character class [a-f0-9\-] that admits only lowercase hex digits, so a valid Azure AD tenant GUID entered in uppercase or mixed case (e.g. A1B2C3D4-E5F6-7890-1234-567890ABCDEF) is rejected as "Invalid Azure AD tenant ID format" before the network validation runs. A second case-sensitivity defect in the same method — the issuer.contains(tenantId) comparison — would reject the same input after the network probe if the regex were fixed alone, because Azure's OpenID-config endpoint returns a lowercase issuer even for an uppercase path-segment GUID. Both are one logical defect: the method treats the tenant GUID as case-sensitive end-to-end instead of normalizing to lowercase.
  • Actual vs. expected: The validator should accept any valid Azure AD tenant GUID regardless of letter case; instead it fails the config save for any non-lowercase GUID.
  • Impact: Configuration-save failure for a narrow input class — an uppercase or mixed-case tenant GUID embedded in the IdP Entity ID. No security or data-integrity consequence; the config simply cannot be saved.

Code with Bug

openmetadata-service/src/main/java/org/openmetadata/service/security/auth/validator/SamlValidator.java

// Validate tenant ID format (should be a GUID)
if (!tenantId.matches("[a-f0-9\\-]{36}") // <-- BUG 🔴 rejects uppercase/mixed-case GUID hex digits
    && !tenantId.equals("common")
    && !tenantId.equals("organizations")
    && !tenantId.equals("consumers")) {
  return ValidationErrorBuilder.createFieldError(
      ValidationErrorBuilder.FieldPaths.SAML_IDP_ENTITY_ID,
      "Invalid Azure AD tenant ID format: " + tenantId);
}
// After fetching OpenID config, compare against Azure's issuer
if (!issuer.contains(tenantId) // <-- BUG 🔴 case-sensitive comparison vs lowercase issuer
    && !tenantId.equals("common")
    && !tenantId.equals("organizations")
    && !tenantId.equals("consumers")) {
  return ValidationErrorBuilder.createFieldError(
      ValidationErrorBuilder.FieldPaths.SAML_IDP_ENTITY_ID,
      "Tenant ID mismatch. Expected tenant: " + tenantId + " but issuer is: " + issuer);
}

Explanation

  • validateAzureAdTenant extracts a tenant ID from the Azure AD entity ID and first gates it with tenantId.matches("[a-f0-9\\-]{36}"). This pattern only permits lowercase a-f, so valid GUIDs containing A-F are rejected immediately with "Invalid Azure AD tenant ID format".
  • Even if the regex were loosened, issuer.contains(tenantId) is also case-sensitive. Azure’s OpenID configuration returns a lowercase issuer even when the request path uses an uppercase GUID, so uppercase/mixed-case input would then fail with "Tenant ID mismatch".
  • There is no upstream normalization on the validation call path; the admin-provided value reaches validateAzureAdTenant verbatim.

Failing Test

@Test
void validateSamlConfigurationRejectsUppercaseAzureTenantGuid() {
  SamlSSOClientConfig samlConfig = baseConfig("http://localhost/unused");
  samlConfig.getIdp().setEntityId("https://sts.windows.net/A1B2C3D4-E5F6-7890-1234-567890ABCDEF/");
  samlConfig.getIdp().setIdpX509Certificate(AZURE_CERT);

  FieldError error =
      validator.validateSamlConfiguration(new AuthenticationConfiguration(), samlConfig);

  assertNotNull(error);
  assertEquals(ValidationErrorBuilder.FieldPaths.SAML_IDP_ENTITY_ID, error.getField());
  assertTrue(error.getError().contains("Invalid Azure AD tenant ID format"));
}

Test output:

REPRO_E2E_RESULT: authenticationConfiguration.samlConfiguration.idp.entityId :: Invalid Azure AD tenant ID format: A1B2C3D4-E5F6-7890-1234-567890ABCDEF
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Recommended Fix

Normalize the tenant ID to lowercase once, before both the regex check and the issuer comparison:

String normalizedTenantId = tenantId.toLowerCase(Locale.ROOT);
if (!normalizedTenantId.matches("[a-f0-9-]{36}")
    && !normalizedTenantId.equals("common")
    && !normalizedTenantId.equals("organizations")
    && !normalizedTenantId.equals("consumers")) {
  return ValidationErrorBuilder.createFieldError(
      ValidationErrorBuilder.FieldPaths.SAML_IDP_ENTITY_ID,
      "Invalid Azure AD tenant ID format: " + tenantId);
}

// Build openIdConfigUrl using normalizedTenantId, and compare with contains
// using normalizedTenantId (or lowercase the issuer side) to avoid mismatch.

History

This bug was introduced in commit eebd3bb. The commit (PR #21839, "Feat(ui): SSO configuration from UI and Scim bot token generate revoke functionality and Group Mapping in settings page") created SamlValidator.java from scratch with 1000 new lines as part of a large multi-author feature adding the entire SSO-from-UI flow (Azure/Okta/Google/Auth0/Cognito/custom-OIDC validators, SCIM bot tokens, group mapping). Both case-sensitivity points — the lowercase-only regex [a-f0-9\\-]{36} and the case-sensitive issuer.contains(tenantId) comparison — were present in this creation commit and never modified by the three subsequent commits to touch the file. The bug slipped in because the new unit tests in the same PR exercised the Azure tenant-ID path exclusively with the lowercase fixture 11111111-2222-3333-4444-555555555555, so the case-only rejection of valid uppercase/mixed-case GUIDs never surfaced in CI.

Activity

  1. added a commit that references this issue on Oct 16, 2026
    b5e441a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Done ✅

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions