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 id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
<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.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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -71,6 +71,12 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
||||||
*/
|
*/
|
||||||
void cancelLogin();
|
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
|
* 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.
|
* 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);
|
forceChallenge(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetFlow() {
|
||||||
|
this.status = FlowStatus.FLOW_RESET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fork() {
|
public void fork() {
|
||||||
this.status = FlowStatus.FORK;
|
this.status = FlowStatus.FORK;
|
||||||
|
|
|
@ -4,6 +4,9 @@ import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.omg.PortableInterceptor.SUCCESSFUL;
|
||||||
|
|
||||||
|
import static org.keycloak.authentication.FlowStatus.*;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -153,62 +156,65 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
public Response processResult(AuthenticationProcessor.Result result) {
|
public Response processResult(AuthenticationProcessor.Result result) {
|
||||||
AuthenticationExecutionModel execution = result.getExecution();
|
AuthenticationExecutionModel execution = result.getExecution();
|
||||||
FlowStatus status = result.getStatus();
|
FlowStatus status = result.getStatus();
|
||||||
if (status == FlowStatus.SUCCESS) {
|
switch (status) {
|
||||||
AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
|
case SUCCESS:
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
|
AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
|
||||||
if (execution.isAlternative()) alternativeSuccessful = true;
|
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
|
||||||
return null;
|
if (execution.isAlternative()) alternativeSuccessful = true;
|
||||||
} else if (status == FlowStatus.FAILED) {
|
return null;
|
||||||
AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
|
case FAILED:
|
||||||
processor.logFailure();
|
AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
|
processor.logFailure();
|
||||||
if (result.getChallenge() != null) {
|
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
|
||||||
return sendChallenge(result, execution);
|
if (result.getChallenge() != null) {
|
||||||
}
|
return sendChallenge(result, execution);
|
||||||
throw new AuthenticationFlowException(result.getError());
|
}
|
||||||
} else if (status == FlowStatus.FORK) {
|
throw new AuthenticationFlowException(result.getError());
|
||||||
AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
|
case FORK:
|
||||||
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
|
AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
|
||||||
throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
|
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
|
||||||
} else if (status == FlowStatus.FORCE_CHALLENGE) {
|
throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
case FORCE_CHALLENGE:
|
||||||
return sendChallenge(result, execution);
|
|
||||||
} else if (status == FlowStatus.CHALLENGE) {
|
|
||||||
AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
|
|
||||||
if (execution.isRequired()) {
|
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
||||||
return sendChallenge(result, execution);
|
return sendChallenge(result, execution);
|
||||||
}
|
case CHALLENGE:
|
||||||
UserModel authenticatedUser = processor.getClientSession().getAuthenticatedUser();
|
AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
|
||||||
if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
|
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);
|
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
||||||
return sendChallenge(result, execution);
|
return sendChallenge(result, execution);
|
||||||
}
|
case ATTEMPTED:
|
||||||
if (execution.isAlternative()) {
|
AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
|
||||||
alternativeChallenge = result.getChallenge();
|
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
|
||||||
challengedAlternativeExecution = execution;
|
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
|
||||||
} else {
|
}
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
|
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
|
||||||
}
|
return null;
|
||||||
return null;
|
case FLOW_RESET:
|
||||||
} else if (status == FlowStatus.FAILURE_CHALLENGE) {
|
AuthenticationProcessor.resetFlow(processor.getClientSession());
|
||||||
AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
|
return processor.authenticate();
|
||||||
processor.logFailure();
|
default:
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
|
||||||
return sendChallenge(result, execution);
|
AuthenticationProcessor.logger.error("Unknown result status");
|
||||||
} else if (status == FlowStatus.ATTEMPTED) {
|
throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response sendChallenge(AuthenticationProcessor.Result result, AuthenticationExecutionModel execution) {
|
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.
|
* 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) {
|
public void validateOTP(AuthenticationFlowContext context) {
|
||||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
if (inputData.containsKey("cancel")) {
|
||||||
|
context.resetFlow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||||
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
|
|
|
@ -153,6 +153,16 @@ public class LoginTotpTest {
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginWithTotpCancel() throws Exception {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
loginTotpPage.assertCurrent();
|
||||||
|
loginTotpPage.cancel();
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginWithTotpInvalidPassword() throws Exception {
|
public void loginWithTotpInvalidPassword() throws Exception {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
|
@ -39,6 +39,9 @@ public class LoginTotpPage extends AbstractPage {
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
@FindBy(css = "input[type=\"submit\"]")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
|
@FindBy(id = "kc-cancel")
|
||||||
|
private WebElement cancelButton;
|
||||||
|
|
||||||
@FindBy(className = "feedback-error")
|
@FindBy(className = "feedback-error")
|
||||||
private WebElement loginErrorMessage;
|
private WebElement loginErrorMessage;
|
||||||
|
|
||||||
|
@ -49,6 +52,10 @@ public class LoginTotpPage extends AbstractPage {
|
||||||
submitButton.click();
|
submitButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
cancelButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
public String getError() {
|
public String getError() {
|
||||||
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
|
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue