keycloak-aplcache

KEYCLOAK-1908

10/14/2015 12:49:36 PM

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
index c5d28a1..e472fff 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-totp.ftl
@@ -25,6 +25,7 @@
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
                     <div class="${properties.kcFormButtonsWrapperClass!}">
                         <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" 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>
             </div>
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
index 9b02eb8..681e76c 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
@@ -72,6 +72,12 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
     void cancelLogin();
 
     /**
+     * Reset the current flow to the beginning and restarts it.
+     *
+     */
+    void resetFlow();
+
+    /**
      * Fork the current flow.  The client session will be cloned and set to point at the realm's browser login flow.  The Response will be the result
      * of this fork.  The previous flow will still be set at the current execution.  This is used by reset password when it sends an email.
      * It sends an email linking to the current flow and redirects the browser to a new browser login flow.
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 409b0e7..b6b3272 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -455,6 +455,11 @@ public class AuthenticationProcessor {
         }
 
         @Override
+        public void resetFlow() {
+            this.status = FlowStatus.FLOW_RESET;
+        }
+
+        @Override
         public void fork() {
             this.status = FlowStatus.FORK;
         }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
index e3427e0..4a0c48d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
@@ -37,6 +37,10 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
 
     public void validateOTP(AuthenticationFlowContext context) {
         MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
+        if (inputData.containsKey("cancel")) {
+            context.resetFlow();
+            return;
+        }
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.TOTP);
         if (password == null) {
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index cf825c0..d9ca0b0 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -4,6 +4,9 @@ import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.UserModel;
+import org.omg.PortableInterceptor.SUCCESSFUL;
+
+import static org.keycloak.authentication.FlowStatus.*;
 
 import javax.ws.rs.core.Response;
 import java.util.Iterator;
@@ -153,62 +156,65 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
     public Response processResult(AuthenticationProcessor.Result result) {
         AuthenticationExecutionModel execution = result.getExecution();
         FlowStatus status = result.getStatus();
-        if (status == FlowStatus.SUCCESS) {
-            AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
-            if (execution.isAlternative()) alternativeSuccessful = true;
-            return null;
-        } else if (status == FlowStatus.FAILED) {
-            AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
-            processor.logFailure();
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
-            if (result.getChallenge() != null) {
-                return sendChallenge(result, execution);
-            }
-            throw new AuthenticationFlowException(result.getError());
-        } else if (status == FlowStatus.FORK) {
-            AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
-            processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
-            throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
-        } else if (status == FlowStatus.FORCE_CHALLENGE) {
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
-        } else if (status == FlowStatus.CHALLENGE) {
-            AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
-            if (execution.isRequired()) {
+        switch (status) {
+            case SUCCESS:
+                AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
+                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+                if (execution.isAlternative()) alternativeSuccessful = true;
+                return null;
+            case FAILED:
+                AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
+                processor.logFailure();
+                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
+                if (result.getChallenge() != null) {
+                    return sendChallenge(result, execution);
+                }
+                throw new AuthenticationFlowException(result.getError());
+            case FORK:
+                AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
+                processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
+                throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
+            case FORCE_CHALLENGE:
                 processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
                 return sendChallenge(result, execution);
-            }
-            UserModel authenticatedUser = processor.getClientSession().getAuthenticatedUser();
-            if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
+            case CHALLENGE:
+                AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
+                if (execution.isRequired()) {
+                    processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    return sendChallenge(result, execution);
+                }
+                UserModel authenticatedUser = processor.getClientSession().getAuthenticatedUser();
+                if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
+                    processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+                    return sendChallenge(result, execution);
+                }
+                if (execution.isAlternative()) {
+                    alternativeChallenge = result.getChallenge();
+                    challengedAlternativeExecution = execution;
+                } else {
+                    processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+                }
+                return null;
+            case FAILURE_CHALLENGE:
+                AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
+                processor.logFailure();
                 processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
                 return sendChallenge(result, execution);
-            }
-            if (execution.isAlternative()) {
-                alternativeChallenge = result.getChallenge();
-                challengedAlternativeExecution = execution;
-            } else {
-                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
-            }
-            return null;
-        } else if (status == FlowStatus.FAILURE_CHALLENGE) {
-            AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
-            processor.logFailure();
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
-            return sendChallenge(result, execution);
-        } else if (status == FlowStatus.ATTEMPTED) {
-            AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
-            if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
-                throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
-            }
-            processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
-            return null;
-        } else {
-            AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
-            AuthenticationProcessor.logger.error("Unknown result status");
-            throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
+            case ATTEMPTED:
+                AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
+                if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
+                    throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
+                }
+                processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
+                return null;
+            case FLOW_RESET:
+                AuthenticationProcessor.resetFlow(processor.getClientSession());
+                return processor.authenticate();
+            default:
+                AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
+                AuthenticationProcessor.logger.error("Unknown result status");
+                throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
         }
-
     }
 
     public Response sendChallenge(AuthenticationProcessor.Result result, AuthenticationExecutionModel execution) {
diff --git a/services/src/main/java/org/keycloak/authentication/FlowStatus.java b/services/src/main/java/org/keycloak/authentication/FlowStatus.java
index 6e6ecbd..c8acbc7 100755
--- a/services/src/main/java/org/keycloak/authentication/FlowStatus.java
+++ b/services/src/main/java/org/keycloak/authentication/FlowStatus.java
@@ -48,6 +48,13 @@ public enum FlowStatus {
      * This flow is being forked.  The current client session is being cloned, reset, and redirected to browser login.
      *
      */
-    FORK
+    FORK,
+
+    /**
+     * This flow was reset to the beginning.  An example is hitting cancel on the OTP page which will bring you back to the
+     * username password page.
+     *
+     */
+    FLOW_RESET
 
 }
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 b37401c..6eda3fa 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
@@ -154,6 +154,16 @@ public class LoginTotpTest {
     }
 
     @Test
+    public void loginWithTotpCancel() throws Exception {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        loginTotpPage.assertCurrent();
+        loginTotpPage.cancel();
+        loginPage.assertCurrent();
+    }
+
+    @Test
     public void loginWithTotpInvalidPassword() throws Exception {
         loginPage.open();
         loginPage.login("test-user@localhost", "invalid");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
index b725a16..232b102 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
@@ -39,6 +39,9 @@ public class LoginTotpPage extends AbstractPage {
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;
 
+    @FindBy(id = "kc-cancel")
+    private WebElement cancelButton;
+
     @FindBy(className = "feedback-error")
     private WebElement loginErrorMessage;
 
@@ -49,6 +52,10 @@ public class LoginTotpPage extends AbstractPage {
         submitButton.click();
     }
 
+    public void cancel() {
+        cancelButton.click();
+    }
+
     public String getError() {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }