diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index 570b9ca59b6..c27fb1efa45 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -38,6 +38,7 @@ public class AuthenticationProcessor { protected EventBuilder event; protected HttpRequest request; protected String flowId; + protected boolean userSessionCreated; public static enum Status { @@ -79,6 +80,14 @@ public class AuthenticationProcessor { return session; } + public UserSessionModel getUserSession() { + return userSession; + } + + public boolean isUserSessionCreated() { + return userSessionCreated; + } + public AuthenticationProcessor setRealm(RealmModel realm) { this.realm = realm; return this; @@ -339,6 +348,40 @@ public class AuthenticationProcessor { throw new AuthException(Error.UNKNOWN_USER); } return authenticationComplete(); + } + + public Response authenticateOnly() throws AuthException { + event.event(EventType.LOGIN); + event.client(clientSession.getClient().getClientId()) + .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) + .detail(Details.AUTH_METHOD, clientSession.getAuthMethod()); + String authType = clientSession.getNote(Details.AUTH_TYPE); + if (authType != null) { + event.detail(Details.AUTH_TYPE, authType); + } + UserModel authUser = clientSession.getAuthenticatedUser(); + validateUser(authUser); + Response challenge = processFlow(flowId); + if (challenge != null) return challenge; + + String username = clientSession.getAuthenticatedUser().getUsername(); + if (userSession == null) { // if no authenticator attached a usersession + userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", false, null, null); + userSession.setState(UserSessionModel.State.LOGGING_IN); + userSessionCreated = true; + } + TokenManager.attachClientSession(userSession, clientSession); + event.user(userSession.getUser()) + .detail(Details.USERNAME, username) + .session(userSession); + + return AuthenticationManager.actionRequired(session, userSession, clientSession, connection, request, uriInfo, event); + } + + public Response finishAuthentication() { + event.success(); + RealmModel realm = clientSession.getRealm(); + return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, connection); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java index 0657e8a8b26..20099f49703 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java @@ -43,15 +43,28 @@ public class AbstractFormAuthenticator { } protected Response invalidUser(AuthenticatorContext context) { - return loginForm(context).setError(Messages.INVALID_USER).createLogin(); + return loginForm(context) + .setError(Messages.INVALID_USER) + .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) + .createLogin(); } protected Response disabledUser(AuthenticatorContext context) { - return loginForm(context).setError(Messages.ACCOUNT_DISABLED).createLogin(); + return loginForm(context) + .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) + .setError(Messages.ACCOUNT_DISABLED).createLogin(); } protected Response temporarilyDisabledUser(AuthenticatorContext context) { - return loginForm(context).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin(); + return loginForm(context) + .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) + .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin(); + } + + protected Response invalidCredentials(AuthenticatorContext context) { + return loginForm(context) + .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) + .setError(Messages.INVALID_USER).createLogin(); } public boolean invalidUser(AuthenticatorContext context, UserModel user) { @@ -62,6 +75,7 @@ public class AbstractFormAuthenticator { return true; } if (!user.isEnabled()) { + context.getEvent().user(user); context.getEvent().error(Errors.USER_DISABLED); Response challengeResponse = disabledUser(context); context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse); @@ -69,6 +83,7 @@ public class AbstractFormAuthenticator { } if (context.getRealm().isBruteForceProtected()) { if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) { + context.getEvent().user(user); context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED); Response challengeResponse = temporarilyDisabledUser(context); context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java index 98f532af01f..3995b3a60d4 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java @@ -35,26 +35,25 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat validatePassword(context); } - protected Response badPassword(AuthenticatorContext context) { - return loginForm(context).setError(Messages.INVALID_USER).createLogin(); - } - - public void validatePassword(AuthenticatorContext context) { MultivaluedMap inputData = context.getHttpRequest().getFormParameters(); List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.PASSWORD); if (password == null) { + if (context.getUser() != null) { + context.getEvent().user(context.getUser()); + } context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = badPassword(context); + Response challengeResponse = invalidCredentials(context); context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); return; } credentials.add(UserCredentialModel.password(password)); boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); if (!valid) { + context.getEvent().user(context.getUser()); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = badPassword(context); + Response challengeResponse = invalidCredentials(context); context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); return; } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java index b5bf2d00ee2..cf0d4fa79da 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java @@ -12,6 +12,7 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.ClientSessionCode; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; import javax.ws.rs.core.MultivaluedMap; @@ -34,7 +35,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A @Override public void authenticate(AuthenticatorContext context) { if (!isActionUrl(context)) { - Response challengeResponse = challenge(context); + Response challengeResponse = challenge(context, null); context.challenge(challengeResponse); return; } @@ -46,34 +47,34 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.TOTP); if (password == null) { - Response challengeResponse = challenge(context); + Response challengeResponse = challenge(context, null); context.challenge(challengeResponse); return; } credentials.add(UserCredentialModel.totp(password)); boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); if (!valid) { - context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = challenge(context); + context.getEvent().user(context.getUser()) + .error(Errors.INVALID_USER_CREDENTIALS); + Response challengeResponse = challenge(context, Messages.INVALID_TOTP); context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); return; } context.success(); } - @Override public boolean requiresUser() { return true; } - protected Response challenge(AuthenticatorContext context) { + protected Response challenge(AuthenticatorContext context, String error) { ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode); LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) .setActionUri(action) .setClientSessionCode(clientSessionCode.getCode()); - + if (error != null) forms.setError(error); return forms.createLoginTotp(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 85e4067f976..a5baa07fb0b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -277,7 +277,21 @@ public class AuthorizationEndpoint { .setUriInfo(uriInfo) .setRequest(request); - return processor.authenticate(); + Response challenge = processor.authenticateOnly(); + + if (challenge != null && prompt != null && prompt.equals("none")) { + if (processor.isUserSessionCreated()) { + session.sessions().removeUserSession(realm, processor.getUserSession()); + } + OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event); + return oauth.cancelLogin(clientSession); + } + + if (challenge == null) { + return processor.finishAuthentication(); + } else { + return challenge; + } } protected Response oldBrowserAuthentication(String accessCode) { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 3ada8798e33..0cfca7a263f 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -425,6 +425,17 @@ public class AuthenticationManager { public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession, ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) { + Response requiredAction = actionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event); + if (requiredAction != null) return requiredAction; + event.success(); + RealmModel realm = clientSession.getRealm(); + return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection); + + } + + public static Response actionRequired(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession, + ClientConnection clientConnection, + HttpRequest request, UriInfo uriInfo, EventBuilder event) { RealmModel realm = clientSession.getRealm(); UserModel user = userSession.getUser(); isForcePasswordUpdateRequired(realm, user); @@ -442,7 +453,7 @@ public class AuthenticationManager { if (!requiredActions.isEmpty()) { Iterator i = user.getRequiredActions().iterator(); String action = i.next(); - + if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isEmpty(user.getEmail())) { if (i.hasNext()) action = i.next(); @@ -502,12 +513,12 @@ public class AuthenticationManager { .createOAuthGrant(clientSession); } } - - event.success(); - return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection); + return null; } - + + + private static void isForcePasswordUpdateRequired(RealmModel realm, UserModel user) { int daysToExpirePassword = realm.getPasswordPolicy().getDaysToExpirePassword(); if(daysToExpirePassword != -1) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java old mode 100644 new mode 100755 index 8086500cdee..6db2d85a32d --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java @@ -18,11 +18,13 @@ import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.rule.KerberosRule; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.testsuite.utils.CredentialHelper; /** * Test of LDAPFederationProvider (Kerberos backed by LDAP) @@ -41,6 +43,7 @@ public class KerberosLdapTest extends AbstractKerberosTest { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm); URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json"); keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user"); @@ -126,7 +129,7 @@ public class KerberosLdapTest extends AbstractKerberosTest { .client("kerberos-app") .user(keycloakRule.getUser("test", "jduke").getId()) .detail(Details.REDIRECT_URI, KERBEROS_APP_URL) - .detail(Details.AUTH_METHOD, "spnego") + //.detail(Details.AUTH_METHOD, "spnego") .detail(Details.USERNAME, "jduke") .assertEvent(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java index 644fff31bf5..c3737809144 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java @@ -145,9 +145,9 @@ public class LoginTest { loginPage.assertCurrent(); - Assert.assertEquals("Invalid username or password.", loginPage.getError()); + Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); - events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent(); + events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); } finally { keycloakRule.configure(new KeycloakRule.KeycloakSetup() { @Override @@ -225,7 +225,7 @@ public class LoginTest { driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none"); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "sso").assertEvent(); + events.expectLogin().user(userId).removeDetail(Details.USERNAME).assertEvent(); } @Test @@ -359,7 +359,7 @@ public class LoginTest { Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); - events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent(); + events.expectLogin().user(userId).assertEvent(); } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java index 45795dac818..e13fa0e8183 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java @@ -110,9 +110,11 @@ public class LoginTotpTest { loginTotpPage.assertCurrent(); loginTotpPage.login("123456"); + loginTotpPage.assertCurrent(); + Assert.assertEquals("Invalid authenticator code.", loginPage.getError()); - loginPage.assertCurrent(); - Assert.assertEquals("Invalid username or password.", loginPage.getError()); + //loginPage.assertCurrent(); // Invalid authenticator code. + //Assert.assertEquals("Invalid username or password.", loginPage.getError()); events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java index 2f78c6dc8e2..f80118b4f46 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java @@ -92,7 +92,7 @@ public class SSOTest { assertTrue(profilePage.isCurrent()); - String sessionId2 = events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId(); + String sessionId2 = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId(); assertEquals(sessionId, sessionId2); @@ -139,7 +139,7 @@ public class SSOTest { oauth2.openLoginForm(); - events.expectLogin().session(login2.getSessionId()).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent(); + events.expectLogin().session(login2.getSessionId()).removeDetail(Details.USERNAME).assertEvent(); Assert.assertEquals(RequestType.AUTH_RESPONSE, RequestType.valueOf(driver2.getTitle())); Assert.assertNotNull(oauth2.getCurrentQuery().get(OAuth2Constants.CODE));