diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java index 7b37c059f93..20d08d09604 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java @@ -126,7 +126,6 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl protected Response challenge(AuthenticationFlowContext context, MultivaluedMap formData) { LoginFormsProvider forms = context.form(); - if (!formData.isEmpty()) forms.setFormData(formData); return forms.createLoginUsernamePassword(); diff --git a/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java b/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java index 22e29d2e3a0..32773974fa7 100644 --- a/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java +++ b/services/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.java @@ -183,7 +183,7 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator { // if re-authenticating in the scope of an organization context.success(); } else { - attempted(context); + attempted(context, username); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/authentication/OrganizationAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/authentication/OrganizationAuthenticationTest.java index 5db6f13ca04..4b1e95cf1cf 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/authentication/OrganizationAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/authentication/OrganizationAuthenticationTest.java @@ -315,6 +315,45 @@ public class OrganizationAuthenticationTest extends AbstractOrganizationTest { } } + @Test + public void testUsernameExposureWhenEnteringEmail() { + OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); + + UserRepresentation member = UserBuilder.create() + .username("secretusername123") // Different from email + .email("contractor@contractor.org") + .firstName("John") + .lastName("Doe") + .enabled(true) + .password(memberPassword) + .build(); + + String memberId = ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), member, memberPassword); + organization.members().addMember(memberId).close(); + + // Enter the email address in the login form + openIdentityFirstLoginPage(member.getEmail(), false, null, false, false); + + // when we enter an email, the attempted username should show the email, not the actual username of the resolved user account + loginPage.assertAttemptedUsernameAvailability(true); + String displayedUsername = loginPage.getAttemptedUsername(); + + assertEquals("Entering email should not expose actual username", member.getEmail(), displayedUsername); + + // Enter email with different case (should still work with case-insensitive comparison) + String upperCaseEmail = member.getEmail().toUpperCase(); + openIdentityFirstLoginPage(upperCaseEmail, false, null, false, false); + + loginPage.assertAttemptedUsernameAvailability(true); + String displayedUsernameUpper = loginPage.getAttemptedUsername(); + assertEquals("Should show what user entered (uppercase email)", upperCaseEmail, displayedUsernameUpper); + + Assert.assertTrue("Password input should be present", loginPage.isPasswordInputPresent()); + + // Clean up + testRealm().users().get(memberId).remove(); + } + private void runOnServer(RunOnServer function) { testingClient.server(bc.consumerRealmName()).run(function); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java index 23008973ad0..0c6a5d967ab 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java @@ -211,7 +211,7 @@ public class PasskeysOrganizationAuthenticationTest extends AbstractWebAuthnVirt // now the passkeys username password page should be presented with username selected. Passkeys still enabled loginPage.assertCurrent(); - MatcherAssert.assertThat(loginPage.getAttemptedUsername(), Matchers.is("userwebauthn")); + MatcherAssert.assertThat(loginPage.getAttemptedUsername(), Matchers.is("UserWebAuthn")); MatcherAssert.assertThat(driver.findElement(By.xpath("//form[@id='webauth']")), Matchers.notNullValue()); loginPage.login("invalid-password"); loginPage.assertCurrent(); @@ -222,13 +222,13 @@ public class PasskeysOrganizationAuthenticationTest extends AbstractWebAuthnVirt .assertEvent(); // correct login now - MatcherAssert.assertThat(loginPage.getAttemptedUsername(), Matchers.is("userwebauthn")); + MatcherAssert.assertThat(loginPage.getAttemptedUsername(), Matchers.is("UserWebAuthn")); MatcherAssert.assertThat(driver.findElement(By.xpath("//form[@id='webauth']")), Matchers.notNullValue()); loginPage.login(getPassword(USERNAME)); appPage.assertCurrent(); events.expectLogin() .user(user.getId()) - .detail(Details.USERNAME, "userwebauthn") + .detail(Details.USERNAME, "UserWebAuthn") .detail(Details.CREDENTIAL_TYPE, Matchers.nullValue()) .assertEvent(); }