diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java index 61eaf5bf0f..1354235465 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java @@ -104,7 +104,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth if (user == null) { dummyHash(context); context.getEvent().error(Errors.USER_NOT_FOUND); - Response challengeResponse = challenge(context, Messages.INVALID_USER); + Response challengeResponse = challenge(context, getDefaultChallengeMessage(context)); context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse); } } @@ -138,7 +138,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME); if (username == null) { context.getEvent().error(Errors.USER_NOT_FOUND); - Response challengeResponse = challenge(context, Messages.INVALID_USER); + Response challengeResponse = challenge(context, getDefaultChallengeMessage(context)); context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse); return null; } @@ -193,7 +193,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth if (password == null || password.isEmpty()) { context.getEvent().user(user); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = challenge(context, Messages.INVALID_USER); + Response challengeResponse = challenge(context, getDefaultChallengeMessage(context)); context.forceChallenge(challengeResponse); context.clearUser(); return false; @@ -206,7 +206,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth } else { context.getEvent().user(user); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = challenge(context, Messages.INVALID_USER); + Response challengeResponse = challenge(context, getDefaultChallengeMessage(context)); context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); if (clearUser) { context.clearUser(); @@ -228,4 +228,8 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth } return false; } + + protected String getDefaultChallengeMessage(AuthenticationFlowContext context) { + return Messages.INVALID_USER; + } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/PasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/PasswordForm.java index 7442b1ee7b..f8e5c2a658 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/PasswordForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/PasswordForm.java @@ -22,6 +22,7 @@ import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.services.messages.Messages; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -53,4 +54,9 @@ public class PasswordForm extends UsernamePasswordForm { protected Response createLoginForm(LoginFormsProvider form) { return form.createLoginPassword(); } + + @Override + protected String getDefaultChallengeMessage(AuthenticationFlowContext context){ + return Messages.INVALID_PASSWORD; + } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java index e272714148..a4e8700f92 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java @@ -19,6 +19,7 @@ package org.keycloak.authentication.authenticators.browser; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.forms.login.LoginFormsProvider; +import org.keycloak.services.messages.Messages; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -43,4 +44,11 @@ public final class UsernameForm extends UsernamePasswordForm { protected Response createLoginForm(LoginFormsProvider form) { return form.createLoginUsername(); } + + @Override + protected String getDefaultChallengeMessage(AuthenticationFlowContext context) { + if (context.getRealm().isLoginWithEmailAllowed()) + return Messages.INVALID_USERNAME_OR_EMAIL; + return Messages.INVALID_USERNAME; + } } 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 912b3de07b..c8b4f2e425 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 @@ -30,6 +30,7 @@ import org.keycloak.models.UserModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.messages.Messages; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -111,4 +112,9 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl public PasswordCredentialProvider getCredentialProvider(KeycloakSession session) { return (PasswordCredentialProvider)session.getProvider(CredentialProvider.class, "keycloak-password"); } + + @Override + protected String getDefaultChallengeMessage(AuthenticationFlowContext context){ + return Messages.INVALID_USER; + } } diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java index 55c2786e62..85514d5c51 100755 --- a/services/src/main/java/org/keycloak/services/messages/Messages.java +++ b/services/src/main/java/org/keycloak/services/messages/Messages.java @@ -26,6 +26,12 @@ public class Messages { public static final String INVALID_USER = "invalidUserMessage"; + public static final String INVALID_USERNAME = "invalidUsernameMessage"; + + public static final String INVALID_USERNAME_OR_EMAIL = "invalidUsernameOrEmailMessage"; + + public static final String INVALID_PASSWORD = "invalidPasswordMessage"; + public static final String INVALID_EMAIL = "invalidEmailMessage"; public static final String ACCOUNT_DISABLED = "accountDisabledMessage"; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginNewAuthTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginNewAuthTest.java index 52eadec6b5..a299b025f6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginNewAuthTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginNewAuthTest.java @@ -55,7 +55,7 @@ public class KcOidcFirstBrokerLoginNewAuthTest extends AbstractInitializedBaseBr // Try bad password first passwordPage.login("bad-password"); - Assert.assertEquals("Invalid username or password.", passwordPage.getError()); + Assert.assertEquals("Invalid password.", passwordPage.getError()); // Try good password passwordPage.login("password"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java index e780cce6be..9384cee5ed 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java @@ -1010,6 +1010,77 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { } } + /** + * This test checks the error messages, when the credentials are invalid and UsernameForm and PasswordForm are separated. + */ + @Test + public void testLoginWithWrongCredentialsMessage() { + UserRepresentation user = testRealm().users().search("test-user@localhost").get(0); + Assert.assertNotNull(user); + + loginPage.open(); + loginPage.assertCurrent(); + loginPage.login(user.getUsername(), "wrong_password"); + + Assert.assertEquals("Invalid username or password.", loginPage.getError()); + events.clear(); + + loginPage.assertCurrent(); + loginPage.login(user.getUsername(), "password"); + + Assert.assertFalse(loginPage.isCurrent()); + events.expectLogin() + .user(user) + .detail(Details.USERNAME, "test-user@localhost") + .assertEvent(); + } + + /** + * This test checks the error messages, when the credentials are invalid and UsernameForm and PasswordForm are separated. + */ + @Test + public void testLoginMultiFactorWithWrongCredentialsMessage() { + UserRepresentation user = testRealm().users().search("test-user@localhost").get(0); + Assert.assertNotNull(user); + + configureBrowserFlowWithAlternativeCredentials(); + try { + RealmRepresentation realm = testRealm().toRepresentation(); + realm.setLoginWithEmailAllowed(false); + testRealm().update(realm); + + loginUsernameOnlyPage.open(); + loginUsernameOnlyPage.assertCurrent(); + loginUsernameOnlyPage.login("non_existing_user"); + Assert.assertEquals("Invalid username.", loginUsernameOnlyPage.getError()); + + realm.setLoginWithEmailAllowed(true); + testRealm().update(realm); + loginUsernameOnlyPage.login("non_existing_user"); + Assert.assertEquals("Invalid username or email.", loginUsernameOnlyPage.getError()); + + loginUsernameOnlyPage.login(user.getUsername()); + + passwordPage.assertCurrent(); + passwordPage.login("wrong_password"); + Assert.assertEquals("Invalid password.", passwordPage.getError()); + + passwordPage.assertCurrent(); + events.clear(); + passwordPage.login("password"); + + Assert.assertFalse(loginUsernameOnlyPage.isCurrent()); + Assert.assertFalse(passwordPage.isCurrent()); + + events.expectLogin() + .user(user) + .detail(Details.USERNAME, "test-user@localhost") + .assertEvent(); + } finally { + revertFlows("browser - alternative"); + } + } + /** * This flow contains: * UsernameForm REQUIRED diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 6aff85e12b..0e1fcee83f 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -157,6 +157,9 @@ client_realm-management=Realm Management client_broker=Broker invalidUserMessage=Invalid username or password. +invalidUsernameMessage=Invalid username. +invalidUsernameOrEmailMessage=Invalid username or email. +invalidPasswordMessage=Invalid password. invalidEmailMessage=Invalid email address. accountDisabledMessage=Account is disabled, contact your administrator. accountTemporarilyDisabledMessage=Account is temporarily disabled; contact your administrator or retry later.