From ddb18c4f175bbfe6a0c45b38552530052c0d4669 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:53:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(auth):=20OidcProviderNameDeriver=20?= =?UTF-8?q?=E2=80=94=20issuer=20URI=20=E2=86=92=20display=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../app/security/OidcProviderNameDeriver.java | 41 ++++++++++++++ .../security/OidcProviderNameDeriverTest.java | 53 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcProviderNameDeriver.java create mode 100644 cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcProviderNameDeriverTest.java diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcProviderNameDeriver.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcProviderNameDeriver.java new file mode 100644 index 00000000..511b8d31 --- /dev/null +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcProviderNameDeriver.java @@ -0,0 +1,41 @@ +package com.cameleer.server.app.security; + +import java.net.URI; + +/** + * Pure utility — derives a display label for an OIDC provider from its issuer URI. + * Used by {@link AuthCapabilitiesController} so the SPA can render + * "Sign in with {providerName}" on the login page. + * + *

Pattern-match only — never network-discover. If the issuer doesn't match a + * known vendor pattern, we return the generic "Single Sign-On" label rather than + * leaking hostnames into the UI. + */ +public final class OidcProviderNameDeriver { + + private static final String GENERIC = "Single Sign-On"; + + private OidcProviderNameDeriver() {} + + public static String deriveName(String issuerUri) { + if (issuerUri == null || issuerUri.isBlank()) { + return GENERIC; + } + String host; + try { + URI uri = URI.create(issuerUri.trim()); + host = uri.getHost(); + } catch (IllegalArgumentException e) { + return GENERIC; + } + if (host == null || host.isBlank()) { + return GENERIC; + } + String h = host.toLowerCase(); + if (h.contains("logto")) return "Logto"; + if (h.contains("keycloak")) return "Keycloak"; + if (h.endsWith("auth0.com")) return "Auth0"; + if (h.endsWith("okta.com") || h.endsWith("oktapreview.com")) return "Okta"; + return GENERIC; + } +} diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcProviderNameDeriverTest.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcProviderNameDeriverTest.java new file mode 100644 index 00000000..24d15871 --- /dev/null +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcProviderNameDeriverTest.java @@ -0,0 +1,53 @@ +package com.cameleer.server.app.security; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** Unit tests for {@link OidcProviderNameDeriver}. No Spring context. */ +class OidcProviderNameDeriverTest { + + @Test + void logtoIssuer_returnsLogto() { + assertThat(OidcProviderNameDeriver.deriveName("https://auth.logto.example/")).isEqualTo("Logto"); + assertThat(OidcProviderNameDeriver.deriveName("https://logto.cameleer.local")).isEqualTo("Logto"); + } + + @Test + void keycloakIssuer_returnsKeycloak() { + assertThat(OidcProviderNameDeriver.deriveName("https://keycloak.example/realms/cameleer")).isEqualTo("Keycloak"); + } + + @Test + void auth0Issuer_returnsAuth0() { + assertThat(OidcProviderNameDeriver.deriveName("https://example.auth0.com/")).isEqualTo("Auth0"); + } + + @Test + void oktaIssuer_returnsOkta() { + assertThat(OidcProviderNameDeriver.deriveName("https://dev-123.okta.com/")).isEqualTo("Okta"); + assertThat(OidcProviderNameDeriver.deriveName("https://login.oktapreview.com/")).isEqualTo("Okta"); + } + + @Test + void unknownIssuer_returnsGenericLabel() { + assertThat(OidcProviderNameDeriver.deriveName("https://idp.example.com/")).isEqualTo("Single Sign-On"); + } + + @Test + void blankOrNullIssuer_returnsGenericLabel() { + assertThat(OidcProviderNameDeriver.deriveName("")).isEqualTo("Single Sign-On"); + assertThat(OidcProviderNameDeriver.deriveName(null)).isEqualTo("Single Sign-On"); + assertThat(OidcProviderNameDeriver.deriveName(" ")).isEqualTo("Single Sign-On"); + } + + @Test + void malformedUri_returnsGenericLabel() { + assertThat(OidcProviderNameDeriver.deriveName("not a url")).isEqualTo("Single Sign-On"); + } + + @Test + void caseInsensitiveMatching() { + assertThat(OidcProviderNameDeriver.deriveName("https://AUTH.LOGTO.EXAMPLE/")).isEqualTo("Logto"); + } +}