KEYCLOAK-11898 Refactor AIA implementation
This commit is contained in:
parent
63abebd993
commit
062841a059
19 changed files with 263 additions and 290 deletions
|
@ -37,12 +37,17 @@ import java.net.URI;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface RequiredActionContext {
|
public interface RequiredActionContext {
|
||||||
public static enum Status {
|
enum Status {
|
||||||
CHALLENGE,
|
CHALLENGE,
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
IGNORE,
|
IGNORE,
|
||||||
FAILURE,
|
FAILURE
|
||||||
CANCELED_AIA
|
}
|
||||||
|
|
||||||
|
enum KcActionStatus {
|
||||||
|
SUCCESS,
|
||||||
|
CANCELLED,
|
||||||
|
ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,10 +145,4 @@ public interface RequiredActionContext {
|
||||||
*/
|
*/
|
||||||
void ignore();
|
void ignore();
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark application-initiated action as canceled by the user.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void cancelAIA();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,8 @@ public final class Constants {
|
||||||
public static final String KEY = "key";
|
public static final String KEY = "key";
|
||||||
|
|
||||||
public static final String KC_ACTION = "kc_action";
|
public static final String KC_ACTION = "kc_action";
|
||||||
|
public static final String KC_ACTION_STATUS = "kc_action_status";
|
||||||
|
public static final String KC_ACTION_EXECUTING = "kc_action_executing";
|
||||||
public static final int KC_ACTION_MAX_AGE = 300;
|
public static final int KC_ACTION_MAX_AGE = 300;
|
||||||
|
|
||||||
public static final String IS_AIA_REQUEST = "IS_AIA_REQUEST";
|
public static final String IS_AIA_REQUEST = "IS_AIA_REQUEST";
|
||||||
|
|
|
@ -137,11 +137,6 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
status = Status.IGNORE;
|
status = Status.IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancelAIA() {
|
|
||||||
status = Status.CANCELED_AIA;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getActionUrl(String code) {
|
public URI getActionUrl(String code) {
|
||||||
ClientModel client = authenticationSession.getClient();
|
ClientModel client = authenticationSession.getClient();
|
||||||
|
|
|
@ -66,7 +66,6 @@ import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
||||||
import static org.keycloak.services.managers.AuthenticationManager.IS_AIA_REQUEST;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -180,7 +179,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
attributes.put("statusCode", status.getStatusCode());
|
attributes.put("statusCode", status.getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authenticationSession != null && authenticationSession.getClientNote(IS_AIA_REQUEST) != null) {
|
if (authenticationSession != null && authenticationSession.getClientNote(Constants.KC_ACTION_EXECUTING) != null) {
|
||||||
attributes.put("isAppInitiatedAction", true);
|
attributes.put("isAppInitiatedAction", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,11 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
String nonce = authSession.getClientNote(OIDCLoginProtocol.NONCE_PARAM);
|
String nonce = authSession.getClientNote(OIDCLoginProtocol.NONCE_PARAM);
|
||||||
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, nonce);
|
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, nonce);
|
||||||
|
|
||||||
|
String kcActionStatus = authSession.getClientNote(Constants.KC_ACTION_STATUS);
|
||||||
|
if (kcActionStatus != null) {
|
||||||
|
redirectUri.addParam(Constants.KC_ACTION_STATUS, kcActionStatus);
|
||||||
|
}
|
||||||
|
|
||||||
// Standard or hybrid flow
|
// Standard or hybrid flow
|
||||||
String code = null;
|
String code = null;
|
||||||
if (responseType.hasResponseType(OIDCResponseType.CODE)) {
|
if (responseType.hasResponseType(OIDCResponseType.CODE)) {
|
||||||
|
|
|
@ -129,8 +129,6 @@ public class AuthenticationManager {
|
||||||
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
|
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
|
||||||
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
|
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
|
||||||
private static final TokenTypeCheck VALIDATE_IDENTITY_COOKIE = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID);
|
private static final TokenTypeCheck VALIDATE_IDENTITY_COOKIE = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID);
|
||||||
public static final String IS_AIA_REQUEST = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.IS_AIA_REQUEST;
|
|
||||||
public static final String IS_SILENT_CANCEL = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.AIA_SILENT_CANCEL;
|
|
||||||
|
|
||||||
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
|
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
|
||||||
if (userSession == null) {
|
if (userSession == null) {
|
||||||
|
@ -904,6 +902,11 @@ public class AuthenticationManager {
|
||||||
return authSession.getRequiredActions().iterator().next();
|
return authSession.getRequiredActions().iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String kcAction = authSession.getClientNote(Constants.KC_ACTION);
|
||||||
|
if (kcAction != null) {
|
||||||
|
return kcAction;
|
||||||
|
}
|
||||||
|
|
||||||
if (client.isConsentRequired()) {
|
if (client.isConsentRequired()) {
|
||||||
|
|
||||||
UserConsentModel grantedConsent = getEffectiveGrantedConsent(session, authSession);
|
UserConsentModel grantedConsent = getEffectiveGrantedConsent(session, authSession);
|
||||||
|
@ -1056,43 +1059,78 @@ public class AuthenticationManager {
|
||||||
List<RequiredActionProviderModel> sortedRequiredActions = sortRequiredActionsByPriority(realm, requiredActions);
|
List<RequiredActionProviderModel> sortedRequiredActions = sortRequiredActionsByPriority(realm, requiredActions);
|
||||||
|
|
||||||
for (RequiredActionProviderModel model : sortedRequiredActions) {
|
for (RequiredActionProviderModel model : sortedRequiredActions) {
|
||||||
RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
|
Response response = executeAction(session, authSession, model, request, event, realm, user, false);
|
||||||
if (factory == null) {
|
if (response != null) {
|
||||||
throw new RuntimeException("Unable to find factory for Required Action: " + model.getProviderId() + " did you forget to declare it in a META-INF/services file?");
|
|
||||||
}
|
|
||||||
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, user, factory);
|
|
||||||
RequiredActionProvider actionProvider = null;
|
|
||||||
try {
|
|
||||||
actionProvider = createRequiredAction(context);
|
|
||||||
} catch (AuthenticationFlowException e) {
|
|
||||||
if (e.getResponse() != null) {
|
|
||||||
return e.getResponse();
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
actionProvider.requiredActionChallenge(context);
|
|
||||||
|
|
||||||
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
|
||||||
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getAuthenticationSession().getProtocol());
|
|
||||||
protocol.setRealm(context.getRealm())
|
|
||||||
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
|
||||||
.setUriInfo(context.getUriInfo())
|
|
||||||
.setEventBuilder(event);
|
|
||||||
Response response = protocol.sendError(context.getAuthenticationSession(), Error.CONSENT_DENIED);
|
|
||||||
event.error(Errors.REJECTED_BY_USER);
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
}
|
||||||
authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, model.getProviderId());
|
|
||||||
return context.getChallenge();
|
String kcAction = authSession.getClientNote(Constants.KC_ACTION);
|
||||||
|
if (kcAction != null) {
|
||||||
|
for (RequiredActionProviderModel m : realm.getRequiredActionProviders()) {
|
||||||
|
if (m.getProviderId().equals(kcAction)) {
|
||||||
|
return executeAction(session, authSession, m, request, event, realm, user, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
|
|
||||||
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, factory.getId()).success();
|
logger.debugv("Requested action {0} not configured for realm", kcAction);
|
||||||
// don't have to perform the same action twice, so remove it from both the user and session required actions
|
setKcActionStatus(kcAction, RequiredActionContext.KcActionStatus.ERROR, authSession);
|
||||||
authSession.getAuthenticatedUser().removeRequiredAction(factory.getId());
|
}
|
||||||
authSession.removeRequiredAction(factory.getId());
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response executeAction(KeycloakSession session, AuthenticationSessionModel authSession, RequiredActionProviderModel model,
|
||||||
|
HttpRequest request, EventBuilder event, RealmModel realm, UserModel user, boolean kcActionExecution) {
|
||||||
|
RequiredActionFactory factory = (RequiredActionFactory) session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
|
||||||
|
if (factory == null) {
|
||||||
|
throw new RuntimeException("Unable to find factory for Required Action: " + model.getProviderId() + " did you forget to declare it in a META-INF/services file?");
|
||||||
|
}
|
||||||
|
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, user, factory);
|
||||||
|
RequiredActionProvider actionProvider = null;
|
||||||
|
try {
|
||||||
|
actionProvider = createRequiredAction(context);
|
||||||
|
} catch (AuthenticationFlowException e) {
|
||||||
|
if (e.getResponse() != null) {
|
||||||
|
return e.getResponse();
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kcActionExecution) {
|
||||||
|
if (actionProvider.initiatedActionSupport() == InitiatedActionSupport.NOT_SUPPORTED) {
|
||||||
|
logger.debugv("Requested action {0} does not support being invoked with kc_action", factory.getId());
|
||||||
|
setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.ERROR, authSession);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
authSession.setClientNote(Constants.KC_ACTION_EXECUTING, factory.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actionProvider.requiredActionChallenge(context);
|
||||||
|
|
||||||
|
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
||||||
|
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getAuthenticationSession().getProtocol());
|
||||||
|
protocol.setRealm(context.getRealm())
|
||||||
|
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
||||||
|
.setUriInfo(context.getUriInfo())
|
||||||
|
.setEventBuilder(event);
|
||||||
|
Response response = protocol.sendError(context.getAuthenticationSession(), Error.CONSENT_DENIED);
|
||||||
|
event.error(Errors.REJECTED_BY_USER);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
||||||
|
authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, model.getProviderId());
|
||||||
|
return context.getChallenge();
|
||||||
|
}
|
||||||
|
else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
|
||||||
|
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, factory.getId()).success();
|
||||||
|
// don't have to perform the same action twice, so remove it from both the user and session required actions
|
||||||
|
authSession.getAuthenticatedUser().removeRequiredAction(factory.getId());
|
||||||
|
authSession.removeRequiredAction(factory.getId());
|
||||||
|
setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.SUCCESS, authSession);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1143,38 +1181,12 @@ public class AuthenticationManager {
|
||||||
public void ignore() {
|
public void ignore() {
|
||||||
throw new RuntimeException("Not allowed to call ignore() within evaluateTriggers()");
|
throw new RuntimeException("Not allowed to call ignore() within evaluateTriggers()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancelAIA() {
|
|
||||||
throw new RuntimeException("Not allowed to call cancelAIA() within evaluateTriggers()");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
evaluateApplicationInitiatedActionTrigger(session, provider, model, authSession);
|
|
||||||
provider.evaluateTriggers(result);
|
provider.evaluateTriggers(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if provider is being requested as an Application-Initiated Action
|
|
||||||
// If so, add it to the authSession.
|
|
||||||
private static void evaluateApplicationInitiatedActionTrigger(final KeycloakSession session,
|
|
||||||
final RequiredActionProvider provider,
|
|
||||||
final RequiredActionProviderModel model,
|
|
||||||
final AuthenticationSessionModel authSession
|
|
||||||
) {
|
|
||||||
if (provider.initiatedActionSupport() == InitiatedActionSupport.NOT_SUPPORTED) return;
|
|
||||||
|
|
||||||
String aia = authSession.getClientNote(Constants.KC_ACTION);
|
|
||||||
if (aia == null) return;
|
|
||||||
|
|
||||||
// make sure you are evaluating the action that was requested
|
|
||||||
if (!aia.equalsIgnoreCase(model.getProviderId())) return;
|
|
||||||
|
|
||||||
authSession.addRequiredAction(model.getProviderId());
|
|
||||||
authSession.removeClientNote(Constants.KC_ACTION); // keep this from being executed twice
|
|
||||||
authSession.setClientNote(IS_AIA_REQUEST, "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
|
public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
|
||||||
boolean isCookie, String tokenString, HttpHeaders headers, Predicate<? super AccessToken>... additionalChecks) {
|
boolean isCookie, String tokenString, HttpHeaders headers, Predicate<? super AccessToken>... additionalChecks) {
|
||||||
try {
|
try {
|
||||||
|
@ -1266,4 +1278,12 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setKcActionStatus(String executedProviderId, RequiredActionContext.KcActionStatus status, AuthenticationSessionModel authSession) {
|
||||||
|
if (executedProviderId.equals(authSession.getClientNote(Constants.KC_ACTION))) {
|
||||||
|
authSession.setClientNote(Constants.KC_ACTION_STATUS, status.name().toLowerCase());
|
||||||
|
authSession.removeClientNote(Constants.KC_ACTION);
|
||||||
|
authSession.removeClientNote(Constants.KC_ACTION_EXECUTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,8 +101,6 @@ import java.net.URI;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS;
|
import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS;
|
||||||
import static org.keycloak.services.managers.AuthenticationManager.IS_AIA_REQUEST;
|
|
||||||
import static org.keycloak.services.managers.AuthenticationManager.IS_SILENT_CANCEL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -993,9 +991,10 @@ public class LoginActionsService {
|
||||||
|
|
||||||
Response response;
|
Response response;
|
||||||
|
|
||||||
if (isCancelAppInitiatedAction(authSession, context)) {
|
if (isCancelAppInitiatedAction(factory.getId(), authSession, context)) {
|
||||||
provider.initiatedActionCanceled(session, authSession);
|
provider.initiatedActionCanceled(session, authSession);
|
||||||
context.cancelAIA();
|
AuthenticationManager.setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.CANCELLED, authSession);
|
||||||
|
context.success();
|
||||||
} else {
|
} else {
|
||||||
provider.processAction(context);
|
provider.processAction(context);
|
||||||
}
|
}
|
||||||
|
@ -1011,16 +1010,13 @@ public class LoginActionsService {
|
||||||
authSession.removeRequiredAction(factory.getId());
|
authSession.removeRequiredAction(factory.getId());
|
||||||
authSession.getAuthenticatedUser().removeRequiredAction(factory.getId());
|
authSession.getAuthenticatedUser().removeRequiredAction(factory.getId());
|
||||||
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
|
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
|
||||||
|
AuthenticationManager.setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.SUCCESS, authSession);
|
||||||
|
|
||||||
response = AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, session.getContext().getUri(), event);
|
response = AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, session.getContext().getUri(), event);
|
||||||
} else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
} else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
||||||
response = context.getChallenge();
|
response = context.getChallenge();
|
||||||
} else if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
} else if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
||||||
response = interruptionResponse(context, authSession, action, Error.CONSENT_DENIED);
|
response = interruptionResponse(context, authSession, action, Error.CONSENT_DENIED);
|
||||||
} else if (isSilentAIACancel(authSession, context)) {
|
|
||||||
response = interruptionResponse(context, authSession, action, Error.CANCELLED_AIA_SILENT);
|
|
||||||
} else if (context.getStatus() == RequiredActionContext.Status.CANCELED_AIA) {
|
|
||||||
response = interruptionResponse(context, authSession, action, Error.CANCELLED_AIA);
|
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unreachable");
|
throw new RuntimeException("Unreachable");
|
||||||
}
|
}
|
||||||
|
@ -1041,21 +1037,13 @@ public class LoginActionsService {
|
||||||
return protocol.sendError(authSession, error);
|
return protocol.sendError(authSession, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCancelAppInitiatedAction(AuthenticationSessionModel authSession, RequiredActionContextResult context) {
|
private boolean isCancelAppInitiatedAction(String providerId, AuthenticationSessionModel authSession, RequiredActionContextResult context) {
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
if (providerId.equals(authSession.getClientNote(Constants.KC_ACTION_EXECUTING))) {
|
||||||
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
boolean userRequestedCancelAIA = formData.getFirst(CANCEL_AIA) != null;
|
boolean userRequestedCancelAIA = formData.getFirst(CANCEL_AIA) != null;
|
||||||
boolean isAIARequest = authSession.getClientNote(IS_AIA_REQUEST) != null;
|
return userRequestedCancelAIA;
|
||||||
|
}
|
||||||
return isAIARequest && userRequestedCancelAIA;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSilentAIACancel(AuthenticationSessionModel authSession, RequiredActionContextResult context) {
|
|
||||||
String silentCancel = authSession.getClientNote(IS_SILENT_CANCEL);
|
|
||||||
boolean isSilentCancel = "true".equalsIgnoreCase(silentCancel);
|
|
||||||
boolean isAIACancel = isCancelAppInitiatedAction(authSession, context);
|
|
||||||
|
|
||||||
return isSilentCancel && isAIACancel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
@ -82,4 +83,12 @@ public class LoginConfigTotpPage extends AbstractPage {
|
||||||
return loginErrorMessage.getText();
|
return loginErrorMessage.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCancelDisplayed() {
|
||||||
|
try {
|
||||||
|
return cancelAIAButton.isDisplayed();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
@ -68,4 +69,13 @@ public class LoginPasswordUpdatePage extends LanguageComboboxAwarePage {
|
||||||
public String getFeedbackMessage() {
|
public String getFeedbackMessage() {
|
||||||
return feedbackMessage.getText();
|
return feedbackMessage.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCancelDisplayed() {
|
||||||
|
try {
|
||||||
|
return cancelAIAButton.isDisplayed();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
@ -91,4 +92,12 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCancelDisplayed() {
|
||||||
|
try {
|
||||||
|
return cancelAIAButton.isDisplayed();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.actions;
|
package org.keycloak.testsuite.actions;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
@ -29,6 +29,11 @@ import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.util.WaitUtils;
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Stan Silvert
|
* @author Stan Silvert
|
||||||
*/
|
*/
|
||||||
|
@ -50,13 +55,8 @@ public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKe
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doAIA() {
|
protected void doAIA() {
|
||||||
doAIA(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doAIA(boolean silentCancel) {
|
|
||||||
UriBuilder builder = OIDCLoginProtocolService.authUrl(authServerPage.createUriBuilder());
|
UriBuilder builder = OIDCLoginProtocolService.authUrl(authServerPage.createUriBuilder());
|
||||||
String uri = builder.queryParam("kc_action", this.aiaAction)
|
String uri = builder.queryParam("kc_action", this.aiaAction)
|
||||||
.queryParam("silent_cancel", Boolean.toString(silentCancel))
|
|
||||||
.queryParam("response_type", "code")
|
.queryParam("response_type", "code")
|
||||||
.queryParam("client_id", "test-app")
|
.queryParam("client_id", "test-app")
|
||||||
.queryParam("scope", "openid")
|
.queryParam("scope", "openid")
|
||||||
|
@ -66,14 +66,24 @@ public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKe
|
||||||
WaitUtils.waitForPageToLoad();
|
WaitUtils.waitForPageToLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertRedirectSuccess() {
|
protected void assertKcActionStatus(String expectedStatus) {
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertCancelMessage() {
|
URI url = null;
|
||||||
String url = this.driver.getCurrentUrl();
|
try {
|
||||||
Assert.assertTrue("Expected 'error=interaction_required' in url", url.contains("error=interaction_required"));
|
url = new URI(this.driver.getCurrentUrl());
|
||||||
Assert.assertTrue("Expected 'error_description=User+cancelled+aplication-initiated+action.' in url", url.contains("error_description=User+cancelled+aplication-initiated+action."));
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
List<NameValuePair> pairs = URLEncodedUtils.parse(url, "UTF-8");
|
||||||
|
String kcActionStatus = null;
|
||||||
|
for (NameValuePair p : pairs) {
|
||||||
|
if (p.getName().equals("kc_action_status")) {
|
||||||
|
kcActionStatus = p.getValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals(expectedStatus, kcActionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertSilentCancelMessage() {
|
protected void assertSilentCancelMessage() {
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.testsuite.actions;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test makes sure that sending a cancel signal does not remove a non-AIA
|
|
||||||
* required action
|
|
||||||
*
|
|
||||||
* @author Stan Silvert
|
|
||||||
*/
|
|
||||||
public class AppInitiatedActionCancelTest extends AbstractAppInitiatedActionTest {
|
|
||||||
|
|
||||||
@Page
|
|
||||||
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
|
|
||||||
|
|
||||||
public AppInitiatedActionCancelTest() {
|
|
||||||
super("update_profile");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
|
||||||
ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
// Verify that sending a "cancel" does not remove the required action.
|
|
||||||
public void cancelUpdateProfile() {
|
|
||||||
doAIA();
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
|
||||||
updateProfilePage.assertCurrent();
|
|
||||||
updateProfilePage.cancel();
|
|
||||||
assertRedirectSuccess();
|
|
||||||
assertCancelMessage();
|
|
||||||
|
|
||||||
appPage.logout();
|
|
||||||
|
|
||||||
loginPage.open();
|
|
||||||
loginPage.assertCurrent();
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
|
||||||
updateProfilePage.assertCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void silentCancelUpdateProfile() {
|
|
||||||
doAIA(true);
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
|
||||||
updateProfilePage.assertCurrent();
|
|
||||||
updateProfilePage.cancel();
|
|
||||||
assertRedirectSuccess();
|
|
||||||
assertSilentCancelMessage();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,23 +17,31 @@
|
||||||
package org.keycloak.testsuite.actions;
|
package org.keycloak.testsuite.actions;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
import org.keycloak.testsuite.util.GreenMailRule;
|
import org.keycloak.testsuite.util.GreenMailRule;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Stan Silvert
|
* @author Stan Silvert
|
||||||
*/
|
*/
|
||||||
public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedActionTest {
|
public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedActionTest {
|
||||||
|
|
||||||
public AppInitiatedActionResetPasswordTest() {
|
public AppInitiatedActionResetPasswordTest() {
|
||||||
super("update_password");
|
super(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,19 +55,28 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
|
||||||
@Page
|
@Page
|
||||||
protected LoginPasswordUpdatePage changePasswordPage;
|
protected LoginPasswordUpdatePage changePasswordPage;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void tempPassword() throws Exception {
|
public void resetPassword() throws Exception {
|
||||||
doAIA();
|
loginPage.open();
|
||||||
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
doAIA();
|
||||||
|
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
|
assertTrue(changePasswordPage.isCancelDisplayed());
|
||||||
|
|
||||||
changePasswordPage.changePassword("new-password", "new-password");
|
changePasswordPage.changePassword("new-password", "new-password");
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
@ -73,6 +90,30 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resetPasswordRequiresReAuth() throws Exception {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
setTimeOffset(350);
|
||||||
|
|
||||||
|
// Should prompt for re-authentication
|
||||||
|
doAIA();
|
||||||
|
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
changePasswordPage.assertCurrent();
|
||||||
|
assertTrue(changePasswordPage.isCancelDisplayed());
|
||||||
|
|
||||||
|
changePasswordPage.changePassword("new-password", "new-password");
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
assertKcActionStatus("success");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cancelChangePassword() throws Exception {
|
public void cancelChangePassword() throws Exception {
|
||||||
doAIA();
|
doAIA();
|
||||||
|
@ -82,8 +123,31 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
changePasswordPage.cancel();
|
changePasswordPage.cancel();
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("cancelled");
|
||||||
assertCancelMessage();
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resetPasswordUserHasUpdatePasswordRequiredAction() throws Exception {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
UserResource userResource = testRealm().users().get(findUser("test-user@localhost").getId());
|
||||||
|
UserRepresentation userRep = userResource.toRepresentation();
|
||||||
|
userRep.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
||||||
|
userResource.update(userRep);
|
||||||
|
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
doAIA();
|
||||||
|
|
||||||
|
changePasswordPage.assertCurrent();
|
||||||
|
assertFalse(changePasswordPage.isCancelDisplayed());
|
||||||
|
|
||||||
|
changePasswordPage.changePassword("new-password", "new-password");
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
|
||||||
|
assertKcActionStatus("success");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.testsuite.actions;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.models.AccountRoles;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
|
||||||
import org.keycloak.testsuite.pages.ErrorPage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Stan Silvert
|
|
||||||
*/
|
|
||||||
public class AppInitiatedActionRoleTest extends AbstractAppInitiatedActionTest {
|
|
||||||
|
|
||||||
@Page
|
|
||||||
protected ErrorPage errorPage;
|
|
||||||
|
|
||||||
public AppInitiatedActionRoleTest() {
|
|
||||||
super("update_profile");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
|
||||||
List<RoleRepresentation> roleList = testRealm.getRoles().getClient().get("test-app");
|
|
||||||
|
|
||||||
RoleRepresentation manageAccountRole = null;
|
|
||||||
for (RoleRepresentation role : roleList) {
|
|
||||||
if (role.getName().equals(AccountRoles.MANAGE_ACCOUNT)) {
|
|
||||||
manageAccountRole = role;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
roleList.remove(manageAccountRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void roleNotSetTest() {
|
|
||||||
loginPage.open();
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
|
||||||
doAIA();
|
|
||||||
errorPage.assertCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.HmacOTP;
|
import org.keycloak.models.utils.HmacOTP;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||||
|
@ -51,7 +52,7 @@ import static org.junit.Assert.assertTrue;
|
||||||
public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionTest {
|
public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionTest {
|
||||||
|
|
||||||
public AppInitiatedActionTotpSetupTest() {
|
public AppInitiatedActionTotpSetupTest() {
|
||||||
super("configure_totp");
|
super(UserModel.RequiredAction.CONFIGURE_TOTP.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,7 +65,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
for (AuthenticationExecutionInfoRepresentation execution : adminClient.realm("test").flows().getExecutions("browser")) {
|
for (AuthenticationExecutionInfoRepresentation execution : adminClient.realm("test").flows().getExecutions("browser")) {
|
||||||
String providerId = execution.getProviderId();
|
String providerId = execution.getProviderId();
|
||||||
if ("auth-otp-form".equals(providerId)) {
|
if ("auth-otp-form".equals(providerId)) {
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED.name());
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL.name());
|
||||||
adminClient.realm("test").flows().updateExecutions("browser", execution);
|
adminClient.realm("test").flows().updateExecutions("browser", execution);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,7 +113,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
|
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
|
||||||
.getDetails().get(Details.CODE_ID);
|
.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
|
events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -126,8 +127,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
totpPage.assertCurrent();
|
totpPage.assertCurrent();
|
||||||
totpPage.cancel();
|
totpPage.cancel();
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("cancelled");
|
||||||
assertCancelMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -297,7 +297,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
|
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
|
||||||
.getDetails().get(Details.CODE_ID);
|
.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
|
EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
|
||||||
|
|
||||||
|
@ -310,8 +310,6 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
|
|
||||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||||
|
|
||||||
assertRedirectSuccess();
|
|
||||||
|
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +331,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
totpPage.configure(totp.generateTOTP(totpCode));
|
totpPage.configure(totp.generateTOTP(totpCode));
|
||||||
|
|
||||||
// After totp config, user should be on the app page
|
// After totp config, user should be on the app page
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
events.poll();
|
events.poll();
|
||||||
events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
|
||||||
|
@ -375,17 +373,6 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
// Try to login
|
// Try to login
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("setupTotp2", "password2");
|
loginPage.login("setupTotp2", "password2");
|
||||||
|
|
||||||
// Since the authentificator was removed, it has to be set up again
|
|
||||||
totpPage.assertCurrent();
|
|
||||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
|
||||||
|
|
||||||
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent()
|
|
||||||
.getDetails().get(Details.CODE_ID);
|
|
||||||
|
|
||||||
assertRedirectSuccess();
|
|
||||||
|
|
||||||
events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -416,7 +403,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
|
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
|
||||||
.getDetails().get(Details.CODE_ID);
|
.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
|
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
|
||||||
|
|
||||||
|
@ -431,7 +418,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
assertEquals(8, token.length());
|
assertEquals(8, token.length());
|
||||||
loginTotpPage.login(token);
|
loginTotpPage.login(token);
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus(null);
|
||||||
|
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
@ -469,7 +456,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
.getDetails().get(Details.CODE_ID);
|
.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
//RequestType reqType = appPage.getRequestType();
|
//RequestType reqType = appPage.getRequestType();
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
|
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
|
||||||
|
|
||||||
oauth.openLogout();
|
oauth.openLogout();
|
||||||
|
@ -481,7 +468,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
String token = otpgen.generateHOTP(totpSecret, 1);
|
String token = otpgen.generateHOTP(totpSecret, 1);
|
||||||
loginTotpPage.login(token);
|
loginTotpPage.login(token);
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus(null);
|
||||||
|
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
@ -506,7 +493,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
||||||
loginTotpPage.assertCurrent();
|
loginTotpPage.assertCurrent();
|
||||||
loginTotpPage.login(token);
|
loginTotpPage.login(token);
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus(null);
|
||||||
|
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
@ -37,7 +38,7 @@ import org.keycloak.testsuite.util.UserBuilder;
|
||||||
public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedActionTest {
|
public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedActionTest {
|
||||||
|
|
||||||
public AppInitiatedActionUpdateProfileTest() {
|
public AppInitiatedActionUpdateProfileTest() {
|
||||||
super("update_profile");
|
super(UserModel.RequiredAction.UPDATE_PROFILE.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
|
@ -85,7 +86,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
||||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
// assert user is really updated in persistent store
|
// assert user is really updated in persistent store
|
||||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||||
|
@ -113,7 +114,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
||||||
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
// assert user is really updated in persistent store
|
// assert user is really updated in persistent store
|
||||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||||
|
@ -132,8 +133,8 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
||||||
updateProfilePage.assertCurrent();
|
updateProfilePage.assertCurrent();
|
||||||
updateProfilePage.cancel();
|
updateProfilePage.cancel();
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("cancelled");
|
||||||
assertCancelMessage();
|
|
||||||
|
|
||||||
// assert nothing was updated in persistent store
|
// assert nothing was updated in persistent store
|
||||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||||
|
@ -164,7 +165,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
||||||
.removeDetail(Details.CONSENT)
|
.removeDetail(Details.CONSENT)
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
assertRedirectSuccess();
|
assertKcActionStatus("success");
|
||||||
|
|
||||||
events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).assertEvent();
|
events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).assertEvent();
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
import org.keycloak.testsuite.util.GreenMailRule;
|
import org.keycloak.testsuite.util.GreenMailRule;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -66,6 +69,8 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
|
assertFalse(changePasswordPage.isCancelDisplayed());
|
||||||
|
|
||||||
changePasswordPage.changePassword("new-password", "new-password");
|
changePasswordPage.changePassword("new-password", "new-password");
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
|
|
@ -131,6 +131,7 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
||||||
String userId = events.expectRegister("setupTotp", "email@mail.com").assertEvent().getUserId();
|
String userId = events.expectRegister("setupTotp", "email@mail.com").assertEvent().getUserId();
|
||||||
|
|
||||||
assertTrue(totpPage.isCurrent());
|
assertTrue(totpPage.isCurrent());
|
||||||
|
assertFalse(totpPage.isCancelDisplayed());
|
||||||
|
|
||||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -92,6 +94,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
updateProfilePage.assertCurrent();
|
updateProfilePage.assertCurrent();
|
||||||
|
assertFalse(updateProfilePage.isCancelDisplayed());
|
||||||
|
|
||||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue