KEYCLOAK-1908
This commit is contained in:
parent
aadd63e2b0
commit
5563118d79
8 changed files with 98 additions and 52 deletions
|
@ -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>
|
||||
|
|
|
@ -71,6 +71,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.
|
||||
|
|
|
@ -454,6 +454,11 @@ public class AuthenticationProcessor {
|
|||
forceChallenge(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetFlow() {
|
||||
this.status = FlowStatus.FLOW_RESET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fork() {
|
||||
this.status = FlowStatus.FORK;
|
||||
|
|
|
@ -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,12 +156,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
|||
public Response processResult(AuthenticationProcessor.Result result) {
|
||||
AuthenticationExecutionModel execution = result.getExecution();
|
||||
FlowStatus status = result.getStatus();
|
||||
if (status == FlowStatus.SUCCESS) {
|
||||
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;
|
||||
} else if (status == FlowStatus.FAILED) {
|
||||
case FAILED:
|
||||
AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
|
||||
processor.logFailure();
|
||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
|
||||
|
@ -166,14 +170,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
|||
return sendChallenge(result, execution);
|
||||
}
|
||||
throw new AuthenticationFlowException(result.getError());
|
||||
} else if (status == FlowStatus.FORK) {
|
||||
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());
|
||||
} else if (status == FlowStatus.FORCE_CHALLENGE) {
|
||||
case FORCE_CHALLENGE:
|
||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
||||
return sendChallenge(result, execution);
|
||||
} else if (status == FlowStatus.CHALLENGE) {
|
||||
case CHALLENGE:
|
||||
AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
|
||||
if (execution.isRequired()) {
|
||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
||||
|
@ -191,24 +195,26 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
|||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
|
||||
}
|
||||
return null;
|
||||
} else if (status == FlowStatus.FAILURE_CHALLENGE) {
|
||||
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);
|
||||
} else if (status == FlowStatus.ATTEMPTED) {
|
||||
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;
|
||||
} else {
|
||||
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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -153,6 +153,16 @@ public class LoginTotpTest {
|
|||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue