KEYCLOAK-15239 Reset Password Success Message not shown when Kerberos is Enabled
This commit is contained in:
parent
dbc6514bfc
commit
6f409d088a
4 changed files with 90 additions and 9 deletions
|
@ -54,11 +54,14 @@ import org.keycloak.services.util.CacheControlUtil;
|
||||||
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.sessions.CommonClientSessionModel;
|
import org.keycloak.sessions.CommonClientSessionModel;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedHashMap;
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -95,12 +98,12 @@ public class AuthenticationProcessor {
|
||||||
/**
|
/**
|
||||||
* This could be an error message forwarded from another authenticator
|
* This could be an error message forwarded from another authenticator
|
||||||
*/
|
*/
|
||||||
protected FormMessage forwardedErrorMessage;
|
protected ForwardedFormMessageStore forwardedErrorMessageStore = new ForwardedFormMessageStore(ForwardedFormMessageType.ERROR);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This could be an success message forwarded from another authenticator
|
* This could be an success message forwarded from another authenticator
|
||||||
*/
|
*/
|
||||||
protected FormMessage forwardedSuccessMessage;
|
protected ForwardedFormMessageStore forwardedSuccessMessageStore = new ForwardedFormMessageStore(ForwardedFormMessageType.SUCCESS);
|
||||||
|
|
||||||
// Used for client authentication
|
// Used for client authentication
|
||||||
protected ClientModel client;
|
protected ClientModel client;
|
||||||
|
@ -212,12 +215,20 @@ public class AuthenticationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticationProcessor setForwardedErrorMessage(FormMessage forwardedErrorMessage) {
|
public AuthenticationProcessor setForwardedErrorMessage(FormMessage forwardedErrorMessage) {
|
||||||
this.forwardedErrorMessage = forwardedErrorMessage;
|
this.forwardedErrorMessageStore.setForwardedMessage(forwardedErrorMessage);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormMessage getAndRemoveForwardedErrorMessage() {
|
||||||
|
FormMessage formMessage = this.forwardedErrorMessageStore.getForwardedMessage();
|
||||||
|
if (formMessage != null) {
|
||||||
|
this.forwardedErrorMessageStore.removeForwardedMessage();
|
||||||
|
}
|
||||||
|
return formMessage;
|
||||||
|
}
|
||||||
|
|
||||||
public AuthenticationProcessor setForwardedSuccessMessage(FormMessage forwardedSuccessMessage) {
|
public AuthenticationProcessor setForwardedSuccessMessage(FormMessage forwardedSuccessMessage) {
|
||||||
this.forwardedSuccessMessage = forwardedSuccessMessage;
|
this.forwardedSuccessMessageStore.setForwardedMessage(forwardedSuccessMessage);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,7 +491,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FormMessage getForwardedErrorMessage() {
|
public FormMessage getForwardedErrorMessage() {
|
||||||
return AuthenticationProcessor.this.forwardedErrorMessage;
|
return AuthenticationProcessor.this.forwardedErrorMessageStore.getForwardedMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -513,8 +524,10 @@ public class AuthenticationProcessor {
|
||||||
.setClientSessionCode(accessCode);
|
.setClientSessionCode(accessCode);
|
||||||
if (getForwardedErrorMessage() != null) {
|
if (getForwardedErrorMessage() != null) {
|
||||||
provider.addError(getForwardedErrorMessage());
|
provider.addError(getForwardedErrorMessage());
|
||||||
|
forwardedErrorMessageStore.removeForwardedMessage();
|
||||||
} else if (getForwardedSuccessMessage() != null) {
|
} else if (getForwardedSuccessMessage() != null) {
|
||||||
provider.addSuccess(getForwardedSuccessMessage());
|
provider.addSuccess(getForwardedSuccessMessage());
|
||||||
|
forwardedSuccessMessageStore.removeForwardedMessage();
|
||||||
}
|
}
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
@ -626,7 +639,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FormMessage getForwardedSuccessMessage() {
|
public FormMessage getForwardedSuccessMessage() {
|
||||||
return AuthenticationProcessor.this.forwardedSuccessMessage;
|
return AuthenticationProcessor.this.forwardedSuccessMessageStore.getForwardedMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormMessage getErrorMessage() {
|
public FormMessage getErrorMessage() {
|
||||||
|
@ -1087,6 +1100,51 @@ public class AuthenticationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This takes care of CRUD of FormMessage to the authenticationSession, so that message can be displayed on the forms in different HTTP request
|
||||||
|
private class ForwardedFormMessageStore {
|
||||||
|
|
||||||
|
private final String messageKey;
|
||||||
|
|
||||||
|
private ForwardedFormMessageStore(ForwardedFormMessageType messageType) {
|
||||||
|
this.messageKey = messageType.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setForwardedMessage(FormMessage message) {
|
||||||
|
try {
|
||||||
|
logger.tracef("Saving message %s to the authentication session under key %s", message, messageKey);
|
||||||
|
getAuthenticationSession().setAuthNote(messageKey, JsonSerialization.writeValueAsString(message));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new RuntimeException("Unexpected exception when serializing formMessage: " + message, ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FormMessage getForwardedMessage() {
|
||||||
|
String note = getAuthenticationSession().getAuthNote(messageKey);
|
||||||
|
try {
|
||||||
|
return note == null ? null : JsonSerialization.readValue(note, FormMessage.class);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new RuntimeException("Unexpected exception when deserializing formMessage JSON: " + note, ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeForwardedMessage() {
|
||||||
|
logger.tracef("Removing message %s from the authentication session", messageKey);
|
||||||
|
getAuthenticationSession().removeAuthNote(messageKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ForwardedFormMessageType {
|
||||||
|
SUCCESS("fwMessageSuccess"), ERROR("fwMessageError");
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
private ForwardedFormMessageType(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,7 +279,8 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
||||||
public Response processFlow() {
|
public Response processFlow() {
|
||||||
|
|
||||||
// KEYCLOAK-16143: Propagate forwarded error messages if present
|
// KEYCLOAK-16143: Propagate forwarded error messages if present
|
||||||
List<FormMessage> errors = processor.forwardedErrorMessage != null ? Collections.singletonList(processor.forwardedErrorMessage) : null;
|
FormMessage forwardedErrorMessage = processor.getAndRemoveForwardedErrorMessage();
|
||||||
|
List<FormMessage> errors = forwardedErrorMessage != null ? Collections.singletonList(forwardedErrorMessage) : null;
|
||||||
|
|
||||||
return renderForm(null, errors);
|
return renderForm(null, errors);
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,7 +362,11 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
|
||||||
|
|
||||||
|
|
||||||
protected AuthenticationExecutionModel.Requirement updateKerberosAuthExecutionRequirement(AuthenticationExecutionModel.Requirement requirement) {
|
protected AuthenticationExecutionModel.Requirement updateKerberosAuthExecutionRequirement(AuthenticationExecutionModel.Requirement requirement) {
|
||||||
Optional<AuthenticationExecutionInfoRepresentation> kerberosAuthExecutionOpt = testRealmResource()
|
return updateKerberosAuthExecutionRequirement(requirement, testRealmResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthenticationExecutionModel.Requirement updateKerberosAuthExecutionRequirement(AuthenticationExecutionModel.Requirement requirement, RealmResource realmResource) {
|
||||||
|
Optional<AuthenticationExecutionInfoRepresentation> kerberosAuthExecutionOpt = realmResource
|
||||||
.flows()
|
.flows()
|
||||||
.getExecutions(DefaultAuthenticationFlows.BROWSER_FLOW)
|
.getExecutions(DefaultAuthenticationFlows.BROWSER_FLOW)
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -376,7 +380,7 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
|
||||||
AuthenticationExecutionModel.Requirement oldRequirement = AuthenticationExecutionModel.Requirement.valueOf(oldRequirementStr);
|
AuthenticationExecutionModel.Requirement oldRequirement = AuthenticationExecutionModel.Requirement.valueOf(oldRequirementStr);
|
||||||
kerberosAuthExecution.setRequirement(requirement.name());
|
kerberosAuthExecution.setRequirement(requirement.name());
|
||||||
|
|
||||||
testRealmResource()
|
realmResource
|
||||||
.flows()
|
.flows()
|
||||||
.updateExecutions(DefaultAuthenticationFlows.BROWSER_FLOW, kerberosAuthExecution);
|
.updateExecutions(DefaultAuthenticationFlows.BROWSER_FLOW, kerberosAuthExecution);
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.common.constants.ServiceAccountConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.utils.SystemClientUtil;
|
import org.keycloak.models.utils.SystemClientUtil;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
|
@ -38,6 +39,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||||
|
import org.keycloak.testsuite.federation.kerberos.AbstractKerberosTest;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.ErrorPage;
|
import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
|
@ -1068,6 +1070,22 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
assertThat(driver2.getPageSource(), Matchers.containsString("Your account has been updated."));
|
assertThat(driver2.getPageSource(), Matchers.containsString("Your account has been updated."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// KEYCLOAK-15239
|
||||||
|
@Test
|
||||||
|
public void resetPasswordWithSpnegoEnabled() throws IOException, MessagingException {
|
||||||
|
// Just switch SPNEGO authenticator requirement to alternative. No real usage of SPNEGO needed for this test
|
||||||
|
AuthenticationExecutionModel.Requirement origRequirement = AbstractKerberosTest.updateKerberosAuthExecutionRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE, testRealm());
|
||||||
|
|
||||||
|
try {
|
||||||
|
resetPassword("login-test");
|
||||||
|
} finally {
|
||||||
|
// Revert
|
||||||
|
AbstractKerberosTest.updateKerberosAuthExecutionRequirement(origRequirement, testRealm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void failResetPasswordServiceAccount() {
|
public void failResetPasswordServiceAccount() {
|
||||||
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "client-user";
|
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "client-user";
|
||||||
|
|
Loading…
Reference in a new issue