keycloak-memoizeit

reset password refactor

8/16/2015 4:20:16 PM

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/login/validate-reset-email.ftl b/forms/common-themes/src/main/resources/theme/base/login/validate-reset-email.ftl
index fd5fa65..5217b04 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/validate-reset-email.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/validate-reset-email.ftl
@@ -16,14 +16,9 @@
             </div>
 
             <div class="${properties.kcFormGroupClass!}">
-                <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
-                    <div class="${properties.kcFormOptionsWrapperClass!}">
-                        <span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
-                    </div>
-                </div>
-
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
+                    <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-submit" type="submit" value="${msg("doLogIn")}"/>
+                    <input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
                 </div>
             </div>
         </form>
diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
index 105b6ba..52705a2 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -80,7 +80,8 @@ public interface ClientSessionModel {
         RECOVER_PASSWORD,
         AUTHENTICATE,
         SOCIAL_CALLBACK,
-        LOGGED_OUT
+        LOGGED_OUT,
+        RESET_CREDENTIALS
     }
 
     public enum ExecutionStatus {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
index 26ec7bd..f4b4431 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
@@ -224,4 +224,11 @@ public interface AuthenticationFlowContext {
      *
      */
     void cancelLogin();
+
+    /**
+     * Abort the current flow and restart it using the realm's browser login
+     *
+     * @return
+     */
+    void resetBrowserLogin();
 }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
index ec60973..e348b67 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
@@ -16,5 +16,6 @@ public enum AuthenticationFlowError {
     USER_CONFLICT,
     USER_TEMPORARILY_DISABLED,
     INTERNAL_ERROR,
-    UNKNOWN_USER
+    UNKNOWN_USER,
+    RESET_TO_BROWSER_LOGIN
 }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 9f3eab9..5caff0b 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -391,6 +391,11 @@ public class AuthenticationProcessor {
             Response response = protocol.cancelLogin(getClientSession());
             forceChallenge(response);
         }
+
+        @Override
+        public void resetBrowserLogin() {
+            this.status = FlowStatus.RESET_BROWSER_LOGIN;
+        }
     }
 
     public void logFailure() {
@@ -434,6 +439,21 @@ public class AuthenticationProcessor {
                 event.error(Errors.EXPIRED_CODE);
                 return ErrorPage.error(session, Messages.EXPIRED_CODE);
 
+            } else if (e.getError() == AuthenticationFlowError.RESET_TO_BROWSER_LOGIN) {
+                resetFlow(getClientSession());
+                AuthenticationProcessor processor = new AuthenticationProcessor();
+                processor.setClientSession(clientSession)
+                        .setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
+                        .setFlowId(realm.getBrowserFlow().getId())
+                        .setConnection(connection)
+                        .setEventBuilder(event)
+                        .setProtector(protector)
+                        .setRealm(realm)
+                        .setSession(session)
+                        .setUriInfo(uriInfo)
+                        .setRequest(request);
+                return processor.authenticate();
+
             } else {
                 event.error(Errors.INVALID_USER_CREDENTIALS);
                 return ErrorPage.error(session, Messages.INVALID_USER);
@@ -530,10 +550,11 @@ public class AuthenticationProcessor {
 
     public void checkClientSession() {
         ClientSessionCode code = new ClientSessionCode(realm, clientSession);
-        if (!code.isValidAction(ClientSessionModel.Action.AUTHENTICATE.name())) {
+        String action = ClientSessionModel.Action.AUTHENTICATE.name();
+        if (!code.isValidAction(action)) {
             throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
         }
-        if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) {
+        if (!code.isActionActive(action)) {
             throw new AuthenticationFlowException(AuthenticationFlowError.EXPIRED_CODE);
         }
         clientSession.setTimestamp(Time.currentTime());
@@ -564,12 +585,16 @@ public class AuthenticationProcessor {
         String username = clientSession.getAuthenticatedUser().getUsername();
         String attemptedUsername = clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME);
         if (attemptedUsername != null) username = attemptedUsername;
+        String rememberMe = clientSession.getNote(Details.REMEMBER_ME);
+        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
         if (userSession == null) { // if no authenticator attached a usersession
-            boolean remember = "true".equals(clientSession.getNote(Details.REMEMBER_ME));
-            userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", remember, null, null);
+            userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), clientSession.getAuthMethod(), remember, null, null);
             userSession.setState(UserSessionModel.State.LOGGING_IN);
             userSessionCreated = true;
         }
+        if (remember) {
+            event.detail(Details.REMEMBER_ME, "true");
+        }
         TokenManager.attachClientSession(userSession, clientSession);
         event.user(userSession.getUser())
                 .detail(Details.USERNAME, username)
@@ -598,21 +623,7 @@ public class AuthenticationProcessor {
     }
 
     protected Response authenticationComplete() {
-        String username = clientSession.getAuthenticatedUser().getUsername();
-        String rememberMe = clientSession.getNote(Details.REMEMBER_ME);
-        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
-        if (userSession == null) { // if no authenticator attached a usersession
-            userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), clientSession.getAuthMethod(), remember, null, null);
-            userSession.setState(UserSessionModel.State.LOGGING_IN);
-        }
-        if (remember) {
-            event.detail(Details.REMEMBER_ME, "true");
-        }
-        TokenManager.attachClientSession(userSession, clientSession);
-        event.user(userSession.getUser())
-                .detail(Details.USERNAME, username)
-                .session(userSession);
-
+        attachSession();
         return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
 
     }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
index 1edd535..a624495 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
@@ -5,6 +5,7 @@ import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.events.Details;
@@ -62,34 +63,22 @@ public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFa
             user =  context.getSession().users().getUserByEmail(username, context.getRealm());
         }
 
-        if (user == null) {
-            event.error(Errors.INVALID_USER_CREDENTIALS);
-            Response challenge = context.form()
-                    .setError(Messages.INVALID_USER)
-                    .createPasswordReset();
-            context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
-            return;
-        }
+        context.getClientSession().setNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
 
-        if (!user.isEnabled()) {
-            event.user(user).error(Errors.USER_DISABLED);
-            Response challenge = context.form()
-                    .setError(Messages.ACCOUNT_DISABLED)
-                    .createPasswordReset();
-            context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
-            return;
-        }
-
-        if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
-            event.user(user).error(Errors.INVALID_EMAIL);
-            Response challenge = context.form()
-                    .setError(Messages.INVALID_EMAIL)
-                    .createPasswordReset();
-            context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
-            return;
+        // we don't want people guessing usernames, so if there is a problem, just continue, but don't set the user
+        // a null user will notify further executions, that this was a failure.
+        if (user == null) {
+            event.clone()
+                    .detail(Details.USERNAME, username)
+                    .error(Errors.USER_NOT_FOUND);
+        } else if (!user.isEnabled()) {
+            event.clone()
+                    .detail(Details.USERNAME, username)
+                    .user(user).error(Errors.USER_DISABLED);
+        } else {
+            context.setUser(user);
         }
 
-        context.setUser(user);
         context.success();
     }
 
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
index 5fb3c00..f5e0165 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
@@ -7,11 +7,13 @@ import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.login.LoginFormsProvider;
@@ -49,13 +51,29 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
     @Override
     public void authenticate(AuthenticationFlowContext context) {
         UserModel user = context.getUser();
+        String username = context.getClientSession().getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME);
+
+        // we don't want people guessing usernames, so if there was a problem obtaining the user, the user will be null.
+        // just redisplay this form
+        if (user == null) {
+            Response challenge = context.form()
+                    .setSuccess(Messages.EMAIL_SENT)
+                    .createForm("validate-reset-email.ftl");
+            context.challenge(challenge);
+            return;
+        }
+
+
         EventBuilder event = context.getEvent();
+        // we don't want people guessing usernames, so if there is a problem, just continuously challenge
         if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
-            event.user(user).error(Errors.INVALID_EMAIL);
+            event.user(user)
+                    .detail(Details.USERNAME, username)
+                    .error(Errors.INVALID_EMAIL);
             Response challenge = context.form()
-                    .setError(Messages.INVALID_EMAIL)
-                    .createPasswordReset();
-            context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
+                    .setSuccess(Messages.EMAIL_SENT)
+                    .createForm("validate-reset-email.ftl");
+            context.challenge(challenge);
             return;
         }
 
@@ -68,14 +86,19 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
         try {
 
             context.getSession().getProvider(EmailProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(secret, link, expiration);
-
-            event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
+            event.clone().event(EventType.SEND_RESET_PASSWORD)
+                         .user(user)
+                         .detail(Details.USERNAME, username)
+                         .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
             Response challenge = context.form()
                     .setSuccess(Messages.EMAIL_SENT)
                     .createForm("validate-reset-email.ftl");
             context.challenge(challenge);
         } catch (EmailException e) {
-            event.error(Errors.EMAIL_SEND_FAILED);
+            event.clone().event(EventType.SEND_RESET_PASSWORD)
+                    .detail(Details.USERNAME, username)
+                    .user(user)
+                    .error(Errors.EMAIL_SEND_FAILED);
             logger.error("Failed to send password reset email", e);
             Response challenge = context.form()
                     .setError(Messages.EMAIL_SENT_ERROR)
@@ -92,7 +115,13 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
             key =context.getUriInfo().getQueryParameters().getFirst(KEY);
 
         } else if (context.getHttpRequest().getHttpMethod().equalsIgnoreCase("POST")) {
-            key = context.getHttpRequest().getDecodedFormParameters().getFirst(KEY);
+            MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+            if (formData.containsKey("cancel")) {
+                context.resetBrowserLogin();
+                return;
+            }
+
+            key = formData.getFirst(KEY);
         }
 
         // Can only guess once!  We remove the note so another guess can't happen
@@ -110,7 +139,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
 
     @Override
     public boolean requiresUser() {
-        return true;
+        return false;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index 49c48ad..df9a26b 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -166,6 +166,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
                 return sendChallenge(result, execution);
             }
             throw new AuthenticationFlowException(result.getError());
+        } else if (status == FlowStatus.RESET_BROWSER_LOGIN) {
+            AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
+            throw new AuthenticationFlowException(AuthenticationFlowError.RESET_TO_BROWSER_LOGIN);
         } else if (status == FlowStatus.FORCE_CHALLENGE) {
             processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
             return sendChallenge(result, execution);
diff --git a/services/src/main/java/org/keycloak/authentication/FlowStatus.java b/services/src/main/java/org/keycloak/authentication/FlowStatus.java
index 0acd875..31b7eee 100755
--- a/services/src/main/java/org/keycloak/authentication/FlowStatus.java
+++ b/services/src/main/java/org/keycloak/authentication/FlowStatus.java
@@ -42,6 +42,12 @@ public enum FlowStatus {
      * a Kerberos authenticator did not see a negotiate header.  There was no error, but the execution was attempted.
      *
      */
-    ATTEMPTED
+    ATTEMPTED,
+
+    /**
+     * Aborting this flow and starting the realm's browser flow from the beginning
+     *
+     */
+    RESET_BROWSER_LOGIN
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f34bed6..d87ad68 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -29,6 +29,7 @@ import org.keycloak.authentication.RequiredActionContext;
 import org.keycloak.authentication.RequiredActionContextResult;
 import org.keycloak.authentication.RequiredActionFactory;
 import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.events.Details;
@@ -762,85 +763,6 @@ public class LoginActionsService {
         }
     }
 
-    private Response sendPasswordReset(@QueryParam("code") String code,
-                                      final MultivaluedMap<String, String> formData) {
-        event.event(EventType.SEND_RESET_PASSWORD);
-        if (!realm.isResetPasswordAllowed()) {
-            event.error(Errors.RESET_CREDENTIAL_DISABLED);
-            return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
-        }
-        Checks checks = new Checks();
-        if (!checks.verifyCode(code)) {
-            return checks.response;
-        }
-        final ClientSessionCode accessCode = checks.clientCode;
-        final ClientSessionModel clientSession = accessCode.getClientSession();
-        ClientModel client = clientSession.getClient();
-
-
-        String username = formData.getFirst("username");
-        if (username == null || username.isEmpty()) {
-            event.error(Errors.USERNAME_MISSING);
-            return session.getProvider(LoginFormsProvider.class)
-                    .setError(Messages.MISSING_USERNAME)
-                    .setClientSessionCode(accessCode.getCode())
-                    .createPasswordReset();
-        }
-
-        event.client(client.getClientId())
-                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.AUTH_METHOD, "form")
-                .detail(Details.USERNAME, username);
-
-        UserModel user = session.users().getUserByUsername(username, realm);
-        if (user == null && username.contains("@")) {
-            user = session.users().getUserByEmail(username, realm);
-        }
-
-        if (user == null) {
-            event.error(Errors.USER_NOT_FOUND);
-        } else if (!user.isEnabled()) {
-            event.user(user).error(Errors.USER_DISABLED);
-        } else if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
-            event.user(user).error(Errors.INVALID_EMAIL);
-        } else {
-            event.user(user);
-
-            UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
-            event.session(userSession);
-            TokenManager.attachClientSession(userSession, clientSession);
-
-            accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name());
-
-            try {
-                UriBuilder builder = Urls.loginResetCredentialsBuilder(uriInfo.getBaseUri());
-                builder.queryParam("key", accessCode.getCode());
-
-                String link = builder.build(realm.getName()).toString();
-                long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
-
-                this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendChangePassword(link, expiration);
-
-                event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
-            } catch (EmailException e) {
-                event.error(Errors.EMAIL_SEND_FAILED);
-                logger.error("Failed to send password reset email", e);
-                return session.getProvider(LoginFormsProvider.class)
-                        .setError(Messages.EMAIL_SENT_ERROR)
-                        .setClientSessionCode(accessCode.getCode())
-                        .createErrorPage();
-            }
-
-            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
-        }
-
-        return session.getProvider(LoginFormsProvider.class)
-                .setSuccess(Messages.EMAIL_SENT)
-                .setClientSessionCode(accessCode.getCode())
-                .createPasswordReset();
-    }
-
     private String getActionCookie() {
         Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
         AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
@@ -857,6 +779,7 @@ public class LoginActionsService {
                 .session(clientSession.getUserSession().getId())
                 .detail(Details.CODE_ID, clientSession.getId())
                 .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
                 .detail(Details.RESPONSE_TYPE, "code");
 
         UserSessionModel userSession = clientSession.getUserSession();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index bcaa98e..95d644e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -167,7 +167,7 @@ public class AccountTest {
         });
     }
 
-    @Test
+    //@Test
     public void ideTesting() throws Exception {
         Thread.sleep(100000000);
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index e3926ef..d8ae5fe 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -44,6 +44,7 @@ import org.keycloak.testsuite.pages.InfoPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginPasswordResetPage;
 import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.ValidatePassworrdEmailResetPage;
 import org.keycloak.testsuite.rule.GreenMailRule;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
@@ -65,6 +66,7 @@ import static org.junit.Assert.*;
  */
 public class ResetPasswordTest {
 
+    static int lifespan = 0;
     @ClassRule
     public static KeycloakRule keycloakRule = new KeycloakRule((new KeycloakRule.KeycloakSetup() {
         @Override
@@ -81,6 +83,7 @@ public class ResetPasswordTest {
 
             user.updateCredential(creds);
             appRealm.setEventsListeners(Collections.singleton("dummy"));
+            lifespan = appRealm.getAccessCodeLifespanUserAction();
         }
     }));
 
@@ -114,6 +117,9 @@ public class ResetPasswordTest {
     protected LoginPasswordResetPage resetPasswordPage;
 
     @WebResource
+    protected ValidatePassworrdEmailResetPage validateResetPage;
+
+    @WebResource
     protected LoginPasswordUpdatePage updatePasswordPage;
 
     @Rule
@@ -133,12 +139,13 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword("login-test");
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
-        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
+                .session((String)null)
+                .user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
 
-        String src = driver.getPageSource();
-        resetPasswordPage.backToLogin();
+        validateResetPage.cancel();
 
         assertTrue(loginPage.isCurrent());
 
@@ -169,17 +176,19 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword("test-user@localhost");
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
-        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, "test-user@localhost").detail(Details.EMAIL, "test-user@localhost").assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, "test-user@localhost")
+                .session((String) null)
+                .detail(Details.EMAIL, "test-user@localhost").assertEvent();
 
-        resetPasswordPage.backToLogin();
+        validateResetPage.cancel();
 
         assertTrue(loginPage.isCurrent());
 
         loginPage.login("login@test.com", "password");
 
-        Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+        Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
 
         String code = oauth.getCurrentQuery().get("code");
         OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
@@ -203,9 +212,14 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword(username);
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
-        String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
+                .user(userId)
+                .detail(Details.USERNAME, username)
+                .detail(Details.EMAIL, "login@test.com")
+                .session((String)null)
+                .assertEvent();
 
         assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
@@ -221,7 +235,7 @@ public class ResetPasswordTest {
 
         updatePasswordPage.changePassword("resetPassword", "resetPassword");
 
-        events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, username).assertEvent().getSessionId();
 
         assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -248,10 +262,10 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword(username);
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
-        String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId)
-                .detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).session((String)null)
+                .detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
 
         assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
@@ -265,12 +279,12 @@ public class ResetPasswordTest {
 
         updatePasswordPage.changePassword(password, password);
 
-        events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId)
-                .detail(Details.USERNAME, username).assertEvent();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId)
+                .detail(Details.USERNAME, username).assertEvent().getSessionId();
 
         assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        events.expectLogin().user(userId).detail(Details.USERNAME, username).session(sessionId).assertEvent();
+        events.expectLogin().user(userId).detail(Details.USERNAME, username).assertEvent();
 
         oauth.openLogout();
 
@@ -285,10 +299,10 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword(username);
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
-        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId)
-                .detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).session((String)null)
+                .detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
 
         assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
@@ -315,13 +329,13 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword("invalid");
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
         assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
         assertEquals(0, greenMail.getReceivedMessages().length);
 
-        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
+        events.expectRequiredAction(EventType.RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
     }
     
     @Test
@@ -339,7 +353,7 @@ public class ResetPasswordTest {
 
         assertEquals(0, greenMail.getReceivedMessages().length);
         
-        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).client((String) null).user((String) null).session((String) null).clearDetails().error("username_missing").assertEvent();
+        events.expectRequiredAction(EventType.RESET_PASSWORD).user((String) null).session((String) null).clearDetails().error("username_missing").assertEvent();
         
     }
 
@@ -353,9 +367,11 @@ public class ResetPasswordTest {
 
             resetPasswordPage.changePassword("login-test");
 
-            resetPasswordPage.assertCurrent();
+            validateResetPage.assertCurrent();
 
-            String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+            events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
+                    .session((String)null)
+                    .user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
 
             assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
@@ -365,13 +381,13 @@ public class ResetPasswordTest {
 
             String changePasswordUrl = getPasswordResetEmailLink(message);
 
-            Time.setOffset(350);
+            Time.setOffset(1800+23);
 
             driver.navigate().to(changePasswordUrl.trim());
 
-            errorPage.assertCurrent();
+            loginPage.assertCurrent();
 
-            assertEquals("Login timeout. Please login again.", errorPage.getError());
+            assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
 
             events.expectRequiredAction(EventType.RESET_PASSWORD).error("expired_code").client("test-app").user((String) null).session((String) null).clearDetails().assertEvent();
         } finally {
@@ -396,13 +412,13 @@ public class ResetPasswordTest {
 
             resetPasswordPage.changePassword("login-test");
 
-            resetPasswordPage.assertCurrent();
+            validateResetPage.assertCurrent();
 
             assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
             assertEquals(0, greenMail.getReceivedMessages().length);
 
-            events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("user_disabled").assertEvent();
+            events.expectRequiredAction(EventType.RESET_PASSWORD).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("user_disabled").assertEvent();
         } finally {
             keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -434,13 +450,13 @@ public class ResetPasswordTest {
 
             resetPasswordPage.changePassword("login-test");
 
-            resetPasswordPage.assertCurrent();
+            validateResetPage.assertCurrent();
 
             assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
             assertEquals(0, greenMail.getReceivedMessages().length);
 
-            events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("invalid_email").assertEvent();
+            events.expectRequiredAction(EventType.RESET_PASSWORD_ERROR).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("invalid_email").assertEvent();
         } finally {
             keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -476,7 +492,9 @@ public class ResetPasswordTest {
 
             assertEquals(0, greenMail.getReceivedMessages().length);
 
-            events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error(Errors.EMAIL_SEND_FAILED).assertEvent();
+            events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).user(userId)
+                    .session((String)null)
+                    .detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error(Errors.EMAIL_SEND_FAILED).assertEvent();
         } finally {
             keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -503,7 +521,7 @@ public class ResetPasswordTest {
 
         resetPasswordPage.changePassword("login-test");
 
-        resetPasswordPage.assertCurrent();
+        validateResetPage.assertCurrent();
 
         assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
@@ -513,7 +531,7 @@ public class ResetPasswordTest {
 
         String changePasswordUrl = getPasswordResetEmailLink(message);
 
-        String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).session((String)null).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
 
         driver.navigate().to(changePasswordUrl.trim());
 
@@ -525,7 +543,7 @@ public class ResetPasswordTest {
 
         updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
 
-        events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
+        String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
 
         assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -585,47 +603,6 @@ public class ResetPasswordTest {
         }
     }
 
-    @Test
-    public void resetPasswordNewBrowserSession() throws IOException, MessagingException {
-        String username = "login-test";
-
-        loginPage.open();
-        loginPage.resetPassword();
-
-        resetPasswordPage.assertCurrent();
-
-        resetPasswordPage.changePassword(username);
-
-        resetPasswordPage.assertCurrent();
-
-        String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
-
-        assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
-
-        assertEquals(1, greenMail.getReceivedMessages().length);
-
-        MimeMessage message = greenMail.getReceivedMessages()[0];
-
-        String changePasswordUrl = getPasswordResetEmailLink(message);
-
-        driver.manage().deleteAllCookies();
-
-        driver.navigate().to(changePasswordUrl.trim());
-
-        updatePasswordPage.assertCurrent();
-
-        updatePasswordPage.changePassword("resetPassword", "resetPassword");
-
-        events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
-
-        assertTrue(infoPage.isCurrent());
-        assertEquals("Your password has been updated.", infoPage.getInfo());
-
-        loginPage.open();
-
-        assertTrue(loginPage.isCurrent());
-    }
-    
     private String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
     	Multipart multipart = (Multipart) message.getContent();
     	
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ValidatePassworrdEmailResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ValidatePassworrdEmailResetPage.java
new file mode 100755
index 0000000..780b704
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ValidatePassworrdEmailResetPage.java
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ValidatePassworrdEmailResetPage extends AbstractPage {
+
+    @FindBy(id = "key")
+    private WebElement keyInput;
+
+    @FindBy(id="kc-submit")
+    private WebElement submitButton;
+
+    @FindBy(id="kc-cancel")
+    private WebElement cancelButton;
+
+    @FindBy(className = "feedback-success")
+    private WebElement emailSuccessMessage;
+
+    @FindBy(className = "feedback-error")
+    private WebElement emailErrorMessage;
+
+    public void submitCode(String code) {
+        keyInput.sendKeys(code);
+
+        submitButton.click();
+    }
+
+    public void cancel() {
+        cancelButton.click();
+    }
+
+    public boolean isCurrent() {
+        return driver.getTitle().equals("Forgot Your Password?");
+    }
+
+    public void open() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getSuccessMessage() {
+        return emailSuccessMessage != null ? emailSuccessMessage.getText() : null;
+    }
+
+    public String getErrorMessage() {
+        return emailErrorMessage != null ? emailErrorMessage.getText() : null;
+    }
+
+}