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 c5d28a1691..e472fff1ff 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 @@
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 b4f638ddff..293f38b97b 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -98,7 +98,8 @@ public interface ClientSessionModel {
SOCIAL_CALLBACK,
LOGGED_OUT,
RESET_CREDENTIALS,
- EXECUTE_ACTIONS
+ EXECUTE_ACTIONS,
+ REQUIRED_ACTIONS
}
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 9b02eb89d0..681e76c484 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
@@ -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.
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 409b0e7311..35b2ab94c8 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -55,6 +55,7 @@ public class AuthenticationProcessor {
protected HttpRequest request;
protected String flowId;
protected String flowPath;
+ protected boolean browserFlow;
/**
* This could be an error message forwarded from another authenticator
*/
@@ -73,6 +74,15 @@ public class AuthenticationProcessor {
public AuthenticationProcessor() {
}
+ public boolean isBrowserFlow() {
+ return browserFlow;
+ }
+
+ public AuthenticationProcessor setBrowserFlow(boolean browserFlow) {
+ this.browserFlow = browserFlow;
+ return this;
+ }
+
public RealmModel getRealm() {
return realm;
}
@@ -454,6 +464,11 @@ public class AuthenticationProcessor {
forceChallenge(response);
}
+ @Override
+ public void resetFlow() {
+ this.status = FlowStatus.FLOW_RESET;
+ }
+
@Override
public void fork() {
this.status = FlowStatus.FORK;
@@ -640,6 +655,31 @@ public class AuthenticationProcessor {
}
}
+ public Response createSuccessRedirect() {
+ // redirect to non-action url so browser refresh button works without reposting past data
+ String code = generateCode();
+
+ URI redirect = LoginActionsService.loginActionsBaseUrl(getUriInfo())
+ .path(flowPath)
+ .queryParam(OAuth2Constants.CODE, code).build(getRealm().getName());
+ return Response.status(302).location(redirect).build();
+
+ }
+
+ public static Response createRequiredActionRedirect(RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) {
+
+ // redirect to non-action url so browser refresh button works without reposting past data
+ ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+ accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
+ clientSession.setTimestamp(Time.currentTime());
+
+ URI redirect = LoginActionsService.loginActionsBaseUrl(uriInfo)
+ .path(LoginActionsService.REQUIRED_ACTION)
+ .queryParam(OAuth2Constants.CODE, accessCode.getCode()).build(realm.getName());
+ return Response.status(302).location(redirect).build();
+
+ }
+
public static void resetFlow(ClientSessionModel clientSession) {
clientSession.setTimestamp(Time.currentTime());
clientSession.setAuthenticatedUser(null);
@@ -773,7 +813,8 @@ public class AuthenticationProcessor {
protected Response authenticationComplete() {
attachSession();
- return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
+ return createRequiredActionRedirect(realm, clientSession, uriInfo);
+ //return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
}
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index cf825c0a07..e4b6dc2e96 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -1,11 +1,19 @@
package org.keycloak.authentication;
+import org.keycloak.OAuth2Constants;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.util.Time;
+import org.omg.PortableInterceptor.SUCCESSFUL;
+
+import static org.keycloak.authentication.FlowStatus.*;
import javax.ws.rs.core.Response;
+import java.net.URI;
import java.util.Iterator;
import java.util.List;
@@ -61,7 +69,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
authenticator.action(result);
Response response = processResult(result);
- if (response == null) return processFlow();
+ if (response == null) {
+ if (result.status == SUCCESS && processor.isBrowserFlow()) {
+ // redirect to a non-action URL so browser refresh works without reposting.
+ return processor.createSuccessRedirect();
+ } else {
+ return processFlow();
+ }
+ }
else return response;
}
}
@@ -153,62 +168,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 6e6ecbd517..c8acbc7465 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/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index c94e8cbc08..9fb9b1ba2a 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -219,8 +219,10 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getClientSession().getAuthenticatedUser());
}
- return null;
+ processor.getClientSession().setExecutionStatus(actionExecution, ClientSessionModel.ExecutionStatus.SUCCESS);
+ // redirect to no execution so browser refresh button works without reposting past data
+ return processor.createSuccessRedirect();
}
public URI getActionUrl(String executionId, String code) {
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
index 9b650c870a..cc37f89e91 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java
@@ -86,7 +86,7 @@ public interface RequiredActionContext {
*
* @return
*/
- String generateAccessCode(String action);
+ String generateCode();
Status getStatus();
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index b9e06f8da4..f2ada5d11c 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -12,6 +12,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.util.Time;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -92,13 +93,6 @@ public class RequiredActionContextResult implements RequiredActionContext {
return httpRequest;
}
- @Override
- public String generateAccessCode(String action) {
- ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
- code.setAction(action);
- return code.getCode();
- }
-
@Override
public Status getStatus() {
return status;
@@ -135,16 +129,24 @@ public class RequiredActionContextResult implements RequiredActionContext {
.build(getRealm().getName());
}
+ @Override
+ public String generateCode() {
+ ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
+ clientSession.setTimestamp(Time.currentTime());
+ return accessCode.getCode();
+ }
+
+
@Override
public URI getActionUrl() {
- String accessCode = generateAccessCode(factory.getId());
+ String accessCode = generateCode();
return getActionUrl(accessCode);
}
@Override
public LoginFormsProvider form() {
- String accessCode = generateAccessCode(factory.getId());
+ String accessCode = generateCode();
URI action = getActionUrl(accessCode);
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
.setUser(getUser())
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 e3427e0a48..4a0c48dc48 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 inputData = context.getHttpRequest().getDecodedFormParameters();
+ if (inputData.containsKey("cancel")) {
+ context.resetFlow();
+ return;
+ }
List credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.TOTP);
if (password == null) {
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 24ff0851d1..87493609cc 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
@@ -105,6 +105,8 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge);
return;
}
+ // We now know email is valid, so set it to valid.
+ context.getUser().setEmailVerified(true);
context.success();
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index 11755b26f9..1a593cb867 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -36,9 +36,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- // if this is EXECUTE_ACTIONS we know that the email sent is valid so we can verify it automatically
- if (context.getClientSession().getNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name()) != null) {
- context.getUser().setEmailVerified(true);
+ if (context.getUser().isEmailVerified()) {
context.success();
return;
}
@@ -52,7 +50,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.VERIFY_EMAIL.name()))
+ .setClientSessionCode(context.generateCode())
.setUser(context.getUser());
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
context.challenge(challenge);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 7c51b74df0..da13ae6334 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -22,10 +22,10 @@ import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
+import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
-import org.keycloak.services.Urls;
import org.keycloak.services.resources.LoginActionsService;
import javax.ws.rs.GET;
@@ -174,7 +174,7 @@ public class AuthorizationEndpoint {
private void checkClient() {
if (clientId == null) {
event.error(Errors.INVALID_REQUEST);
- throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM );
+ throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
}
event.client(clientId);
@@ -182,12 +182,12 @@ public class AuthorizationEndpoint {
client = realm.getClientByClientId(clientId);
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
- throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND );
+ throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND);
}
if (client.isBearerOnly()) {
event.error(Errors.NOT_ALLOWED);
- throw new ErrorPageException(session, Messages.BEARER_ONLY );
+ throw new ErrorPageException(session, Messages.BEARER_ONLY);
}
if (client.isDirectGrantsOnly()) {
@@ -204,7 +204,7 @@ public class AuthorizationEndpoint {
responseType = legacyResponseType;
} else {
event.error(Errors.INVALID_REQUEST);
- throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
+ throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
}
}
@@ -216,7 +216,7 @@ public class AuthorizationEndpoint {
}
} else {
event.error(Errors.INVALID_REQUEST);
- throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
+ throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
}
}
@@ -280,29 +280,43 @@ public class AuthorizationEndpoint {
String flowId = flow.getId();
AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.AUTHENTICATE_PATH);
- Response challenge = null;
- try {
- challenge = processor.authenticateOnly();
+ if (prompt != null && prompt.equals("none")) {
+ // OIDC prompt == NONE
+ // This means that client is just checking if the user is already completely logged in.
+ //
+ // here we cancel login if any authentication action or required action is required
+ Response challenge = null;
+ try {
+ challenge = processor.authenticateOnly();
+ if (challenge == null) {
+ challenge = processor.attachSessionExecutionRequiredActions();
+ }
+ } catch (Exception e) {
+ return processor.handleBrowserException(e);
+ }
+
+ if (challenge != null) {
+ if (processor.isUserSessionCreated()) {
+ session.sessions().removeUserSession(realm, processor.getUserSession());
+ }
+ OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
+ return oauth.cancelLogin(clientSession);
+ }
+
if (challenge == null) {
- challenge = processor.attachSessionExecutionRequiredActions();
+ return processor.finishAuthentication();
+ } else {
+ RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
+ return challenge;
}
- } catch (Exception e) {
- return processor.handleBrowserException(e);
- }
-
- if (challenge != null && prompt != null && prompt.equals("none")) {
- if (processor.isUserSessionCreated()) {
- session.sessions().removeUserSession(realm, processor.getUserSession());
- }
- OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
- return oauth.cancelLogin(clientSession);
- }
-
- if (challenge == null) {
- return processor.finishAuthentication();
} else {
- RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
- return challenge;
+ try {
+ RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
+ return processor.authenticate();
+ } catch (Exception e) {
+ return processor.handleBrowserException(e);
+ }
+
}
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 332d71d136..f31d1ff8c8 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -68,6 +68,7 @@ public class AuthenticationManager {
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
+ public static final String CURRENT_REQUIRED_ACTION = "CURRENT_REQUIRED_ACTION";
protected BruteForceProtector protector;
@@ -525,6 +526,7 @@ public class AuthenticationManager {
return protocol.consentDenied(context.getClientSession());
}
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
+ clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
return context.getChallenge();
}
else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index 6ebe4f32e2..672bb2ad71 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -73,8 +73,12 @@ public class ClientSessionCode {
}
public static ParseResult parseResult(String code, KeycloakSession session, RealmModel realm) {
+ ParseResult result = new ParseResult();
+ if (code == null) {
+ result.illegalHash = true;
+ return result;
+ }
try {
- ParseResult result = new ParseResult();
String[] parts = code.split("\\.");
String id = parts[1];
@@ -93,7 +97,8 @@ public class ClientSessionCode {
result.code = new ClientSessionCode(realm, clientSession);
return result;
} catch (RuntimeException e) {
- return null;
+ result.illegalHash = true;
+ return result;
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 9bbfcf5208..e6be392f3a 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -322,8 +322,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
LOGGER.debugf("Performing local authentication for user [%s].", federatedUser);
}
- return AuthenticationManager.nextActionAfterAuthentication(this.session, userSession, clientSession, this.clientConnection, this.request,
- this.uriInfo, event);
+ return AuthenticationProcessor.createRequiredActionRedirect(realmModel, clientSession, uriInfo);
}
@Override
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 b6e4ce300e..6e525341ce 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -85,6 +85,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
+import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -99,6 +100,7 @@ public class LoginActionsService {
public static final String AUTHENTICATE_PATH = "authenticate";
public static final String REGISTRATION_PATH = "registration";
public static final String RESET_CREDENTIALS_PATH = "reset-credentials";
+ public static final String REQUIRED_ACTION = "required-action";
private RealmModel realm;
@@ -167,12 +169,22 @@ public class LoginActionsService {
boolean verifyCode(String code, String requiredAction) {
if (!verifyCode(code)) {
return false;
- } else if (!clientCode.isValidAction(requiredAction)) {
+ }
+ if (!verifyAction(requiredAction)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public boolean verifyAction(String requiredAction) {
+ if (!clientCode.isValidAction(requiredAction)) {
event.client(clientCode.getClientSession().getClient());
event.error(Errors.INVALID_CODE);
response = ErrorPage.error(session, Messages.INVALID_CODE);
return false;
- } else if (!clientCode.isActionActive(requiredAction)) {
+ }
+ if (!clientCode.isActionActive(requiredAction)) {
event.client(clientCode.getClientSession().getClient());
event.clone().error(Errors.EXPIRED_CODE);
if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
@@ -182,35 +194,8 @@ public class LoginActionsService {
}
response = ErrorPage.error(session, Messages.EXPIRED_CODE);
return false;
- } else {
- return true;
- }
- }
-
- boolean verifyCode(String code, String requiredAction, String alternativeRequiredAction) {
- if (!verifyCode(code)) {
- return false;
- } else if (!(clientCode.isValidAction(requiredAction) || clientCode.isValidAction(alternativeRequiredAction))) {
- event.client(clientCode.getClientSession().getClient());
- event.error(Errors.INVALID_CODE);
- response = ErrorPage.error(session, Messages.INVALID_CODE);
- return false;
- } else if (!(clientCode.isActionActive(requiredAction) || clientCode.isActionActive(alternativeRequiredAction))) {
- event.client(clientCode.getClientSession().getClient());
- event.clone().error(Errors.EXPIRED_CODE);
- if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
- AuthenticationProcessor.resetFlow(clientCode.getClientSession());
- response = processAuthentication(null, clientCode.getClientSession(), Messages.LOGIN_TIMEOUT);
- } else {
- if (clientCode.getClientSession().getUserSession() == null) {
- session.sessions().removeClientSession(realm, clientCode.getClientSession());
- }
- response = ErrorPage.error(session, Messages.EXPIRED_CODE);
- }
- return false;
- } else {
- return true;
}
+ return true;
}
public boolean verifyCode(String code) {
@@ -298,6 +283,7 @@ public class LoginActionsService {
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
.setFlowPath(flowPath)
+ .setBrowserFlow(true)
.setFlowId(flow.getId())
.setConnection(clientConnection)
.setEventBuilder(event)
@@ -559,11 +545,17 @@ public class LoginActionsService {
event.event(EventType.VERIFY_EMAIL);
if (key != null) {
Checks checks = new Checks();
- if (!checks.verifyCode(key, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
+ if (!checks.verifyCode(key, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
ClientSessionModel clientSession = accessCode.getClientSession();
+ if (!ClientSessionModel.Action.VERIFY_EMAIL.name().equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) {
+ logger.error("required action doesn't match current required action");
+ event.error(Errors.INVALID_CODE);
+ throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+ }
+
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
initEvent(clientSession);
@@ -583,10 +575,10 @@ public class LoginActionsService {
event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
- return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
+ return AuthenticationProcessor.createRequiredActionRedirect(realm, clientSession, uriInfo);
} else {
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -619,9 +611,11 @@ public class LoginActionsService {
return checks.response;
}
ClientSessionModel clientSession = checks.clientCode.getClientSession();
+ // verify user email as we know it is valid as this entry point would never have gotten here.
+ clientSession.getUserSession().getUser().setEmailVerified(true);
clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
- return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
+ return AuthenticationProcessor.createRequiredActionRedirect(realm, clientSession, uriInfo);
} else {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
@@ -658,7 +652,7 @@ public class LoginActionsService {
}
}
- @Path("required-action")
+ @Path(REQUIRED_ACTION)
@POST
public Response requiredActionPOST(@QueryParam("code") final String code,
@QueryParam("action") String action) {
@@ -668,7 +662,7 @@ public class LoginActionsService {
}
- @Path("required-action")
+ @Path(REQUIRED_ACTION)
@GET
public Response requiredActionGET(@QueryParam("code") final String code,
@QueryParam("action") String action) {
@@ -678,22 +672,8 @@ public class LoginActionsService {
public Response processRequireAction(final String code, String action) {
event.event(EventType.CUSTOM_REQUIRED_ACTION);
event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
- if (action == null) {
- logger.error("required action query param was null");
- event.error(Errors.INVALID_CODE);
- throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
-
- }
-
- RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
- if (factory == null) {
- logger.error("required action provider was null");
- event.error(Errors.INVALID_CODE);
- throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
- }
- RequiredActionProvider provider = factory.create(session);
Checks checks = new Checks();
- if (!checks.verifyCode(code, action)) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
return checks.response;
}
final ClientSessionCode clientCode = checks.clientCode;
@@ -704,24 +684,31 @@ public class LoginActionsService {
event.error(Errors.USER_SESSION_NOT_FOUND);
throw new WebApplicationException(ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE));
}
+ if (action == null && clientSession.getUserSession() != null) { // do next required action only if user is already authenticated
+ initEvent(clientSession);
+ event.event(EventType.LOGIN);
+ return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
+ }
+
+ if (!action.equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) {
+ logger.error("required action doesn't match current required action");
+ event.error(Errors.INVALID_CODE);
+ throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+ }
+
+ RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
+ if (factory == null) {
+ logger.error("required action provider was null");
+ event.error(Errors.INVALID_CODE);
+ throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+ }
+ RequiredActionProvider provider = factory.create(session);
initEvent(clientSession);
event.event(EventType.CUSTOM_REQUIRED_ACTION);
RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), factory) {
- @Override
- public String generateAccessCode(String action) {
- String clientSessionAction = clientSession.getAction();
- if (action.equals(clientSessionAction)) {
- clientSession.setTimestamp(Time.currentTime());
- return code;
- }
- ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
- code.setAction(action);
- return code.getCode();
- }
-
@Override
public void ignore() {
throw new RuntimeException("Cannot call ignore within processAction()");
@@ -729,13 +716,16 @@ public class LoginActionsService {
};
provider.processAction(context);
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
- event.clone().success();
+ event.success();
// do both
clientSession.removeRequiredAction(factory.getId());
clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
- event.event(EventType.LOGIN);
- return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
- }
+ // redirect to a generic code URI so that browser refresh will work
+ URI redirect = LoginActionsService.loginActionsBaseUrl(uriInfo)
+ .path(LoginActionsService.REQUIRED_ACTION)
+ .queryParam(OAuth2Constants.CODE, code).build(realm.getName());
+ return Response.status(302).location(redirect).build();
+ }
if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
return context.getChallenge();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
old mode 100644
new mode 100755
index 95bff4121d..68edaa1710
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
@@ -91,7 +91,7 @@ public class ResetCredentialsTest extends AbstractAccountManagementTest {
log.info("navigating to " + url);
driver.navigate().to(url);
- assertCurrentUrlStartsWith(testRealmResetCredentialsPage);
+ //assertCurrentUrlStartsWith(testRealmResetCredentialsPage);
testRealmResetCredentialsPage.updatePassword("newPassword");
assertCurrentUrlStartsWith(testRealmAccountManagementPage);
testRealmAccountManagementPage.signOut();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index 8eb05b64e1..294fdb5060 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -175,6 +175,7 @@ public abstract class AbstractKerberosTest {
events.clear();
Response spnegoResponse = spnegoLogin("jduke", "theduke");
Assert.assertEquals(302, spnegoResponse.getStatus());
+ String redirect = spnegoResponse.getLocation().toString();
events.expectLogin()
.client("kerberos-app")
.user(keycloakRule.getUser("test", "jduke").getId())
@@ -244,6 +245,13 @@ public abstract class AbstractKerberosTest {
spnegoSchemeFactory.setCredentials(username, password);
Response response = client.target(kcLoginPageLocation).request().get();
SpnegoAuthenticator.bypassChallengeJavascript = false;
+ if (response.getStatus() == 302) {
+ if (response.getLocation() == null) return response;
+ String uri = response.getLocation().toString();
+ if (uri.contains("login-actions/required-action")) {
+ response = client.target(uri).request().get();
+ }
+ }
return response;
}
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 b37401ca94..6eda3fa30d 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
@@ -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();
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 b725a169b9..232b102663 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;
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
index d5d7296049..9cabdb4189 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
@@ -172,6 +172,14 @@ public class AccessTokenPerfTest {
URI uri = null;
Assert.assertEquals(302, response.getStatus());
uri = response.getLocation();
+ if (response.getStatus() == 302) {
+ while (uri.toString().contains("login-actions/")) {
+ response = client.target(uri).request().get();
+ Assert.assertEquals(302, response.getStatus());
+ uri = response.getLocation();
+ }
+ }
+
for (String header : response.getHeaders().keySet()) {
for (Object value : response.getHeaders().get(header)) {
System.out.println(header + ": " + value);
diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java
index a2e4b42b6a..d158555756 100755
--- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java
+++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/Jetty8Test.java
@@ -85,9 +85,11 @@ public class Jetty8Test {
@AfterClass
public static void shutdownJetty() throws Exception {
- server.stop();
- server.destroy();
- Thread.sleep(1000);
+ try {
+ server.stop();
+ server.destroy();
+ Thread.sleep(100);
+ } catch (Exception e) {}
}
@Rule
diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index 916d855e43..1506b0ce6a 100755
--- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -97,9 +97,11 @@ public class JettySamlTest {
@AfterClass
public static void shutdownJetty() throws Exception {
- server.stop();
- server.destroy();
- Thread.sleep(1000);
+ try {
+ server.stop();
+ server.destroy();
+ Thread.sleep(100);
+ } catch (Exception e) {}
}
@Test
diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java
index fcf75ca465..402779b977 100755
--- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java
+++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/Jetty9Test.java
@@ -85,9 +85,11 @@ public class Jetty9Test {
@AfterClass
public static void shutdownJetty() throws Exception {
- server.stop();
- server.destroy();
- Thread.sleep(1000);
+ try {
+ server.stop();
+ server.destroy();
+ Thread.sleep(100);
+ } catch (Exception e) {}
}
@Rule
diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index c6092b4f8c..e71887e203 100755
--- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -96,9 +96,11 @@ public class JettySamlTest {
@AfterClass
public static void shutdownJetty() throws Exception {
- server.stop();
- server.destroy();
- Thread.sleep(1000);
+ try {
+ server.stop();
+ server.destroy();
+ Thread.sleep(100);
+ } catch (Exception e) {}
}
@Test
diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java
index 037769f9d0..15ee763eed 100755
--- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java
+++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/Jetty9Test.java
@@ -85,8 +85,11 @@ public class Jetty9Test {
@AfterClass
public static void shutdownJetty() throws Exception {
- server.stop();
- server.destroy();
+ try {
+ server.stop();
+ server.destroy();
+ Thread.sleep(100);
+ } catch (Exception e) {}
}
@Rule
diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
index 96709ad20d..e71887e203 100755
--- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
+++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java
@@ -96,8 +96,11 @@ public class JettySamlTest {
@AfterClass
public static void shutdownJetty() throws Exception {
- server.stop();
- server.destroy();
+ try {
+ server.stop();
+ server.destroy();
+ Thread.sleep(100);
+ } catch (Exception e) {}
}
@Test