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;
}