Merge pull request #1558 from patriot1burke/master

refactor update password
This commit is contained in:
Bill Burke 2015-08-21 17:27:42 -04:00
commit 945673c7da
8 changed files with 74 additions and 80 deletions

View file

@ -67,7 +67,8 @@ public enum EventType {
IDENTITY_PROVIDER_ACCCOUNT_LINKING(false), IDENTITY_PROVIDER_ACCCOUNT_LINKING(false),
IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false), IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false),
IMPERSONATE(true), IMPERSONATE(true),
CUSTOM_REQUIRED_ACTION(true); CUSTOM_REQUIRED_ACTION(true),
CUSTOM_REQUIRED_ACTION_ERROR(true);
private boolean saveByDefault; private boolean saveByDefault;

View file

@ -7,8 +7,11 @@
<#elseif section = "form"> <#elseif section = "form">
<div id="kc-info-message"> <div id="kc-info-message">
<p class="instruction">${message.summary}</p> <p class="instruction">${message.summary}</p>
<#if client.baseUrl??> <#if skipLink??>
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p> <#else>
<#if client.baseUrl??>
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
</#if>
</#if> </#if>
</div> </div>
</#if> </#if>

View file

@ -5,7 +5,7 @@
<#elseif section = "header"> <#elseif section = "header">
${msg("updatePasswordTitle")} ${msg("updatePasswordTitle")}
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginUpdatePasswordUrl}" method="post"> <form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label> <label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label>

View file

@ -5,14 +5,23 @@ import org.keycloak.Config;
import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelException;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -49,17 +58,48 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
@Override @Override
public void requiredActionChallenge(RequiredActionContext context) { public void requiredActionChallenge(RequiredActionContext context) {
LoginFormsProvider loginFormsProvider = context.getSession() Response challenge = context.form().createForm("login-update-password.ftl");
.getProvider(LoginFormsProvider.class)
.setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PASSWORD.name()))
.setUser(context.getUser());
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
context.challenge(challenge); context.challenge(challenge);
} }
@Override @Override
public void processAction(RequiredActionContext context) { public void processAction(RequiredActionContext context) {
context.failure(); EventBuilder event = context.getEvent();
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
event.event(EventType.UPDATE_PASSWORD);
String passwordNew = formData.getFirst("password-new");
String passwordConfirm = formData.getFirst("password-confirm");
if (Validation.isBlank(passwordNew)) {
Response challenge = context.form()
.setError(Messages.MISSING_PASSWORD)
.createForm("login-update-password.ftl");
context.challenge(challenge);
return;
} else if (!passwordNew.equals(passwordConfirm)) {
Response challenge = context.form()
.setError(Messages.NOTMATCH_PASSWORD)
.createForm("login-update-password.ftl");
context.challenge(challenge);
return;
}
try {
context.getSession().users().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew));
context.success();
} catch (ModelException me) {
Response challenge = context.form()
.setError(me.getMessage(), me.getParameters())
.createForm("login-update-password.ftl");
context.challenge(challenge);
return;
} catch (Exception ape) {
Response challenge = context.form()
.setError(ape.getMessage())
.createForm("login-update-password.ftl");
context.challenge(challenge);
return;
}
} }
@Override @Override

View file

@ -32,6 +32,7 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie; import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.IdentityBrokerService; import org.keycloak.services.resources.IdentityBrokerService;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
@ -57,6 +58,7 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticationManager { public class AuthenticationManager {
public static final String END_AFTER_REQUIRED_ACTIONS = "END_AFTER_REQUIRED_ACTIONS";
protected static Logger logger = Logger.getLogger(AuthenticationManager.class); protected static Logger logger = Logger.getLogger(AuthenticationManager.class);
public static final String FORM_USERNAME = "username"; public static final String FORM_USERNAME = "username";
// used for auth login // used for auth login
@ -409,6 +411,15 @@ public class AuthenticationManager {
HttpRequest request, UriInfo uriInfo, EventBuilder event) { HttpRequest request, UriInfo uriInfo, EventBuilder event) {
Response requiredAction = actionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event); Response requiredAction = actionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event);
if (requiredAction != null) return requiredAction; if (requiredAction != null) return requiredAction;
if (clientSession.getNote(END_AFTER_REQUIRED_ACTIONS) != null) {
Response response = session.getProvider(LoginFormsProvider.class)
.setAttribute("skipLink", true)
.setSuccess(Messages.ACCOUNT_UPDATED)
.createInfoPage();
session.sessions().removeUserSession(session.getContext().getRealm(), userSession);
return response;
}
event.success(); event.success();
RealmModel realm = clientSession.getRealm(); RealmModel realm = clientSession.getRealm();
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection); return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);

View file

@ -628,67 +628,6 @@ public class LoginActionsService {
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
} }
@Path("password")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updatePassword(@QueryParam("code") String code,
final MultivaluedMap<String, String> formData) {
event.event(EventType.UPDATE_PASSWORD);
Checks checks = new Checks();
if (!checks.verifyCode(code, ClientSessionModel.Action.UPDATE_PASSWORD.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
ClientSessionModel clientSession = accessCode.getClientSession();
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
initEvent(clientSession);
String passwordNew = formData.getFirst("password-new");
String passwordConfirm = formData.getFirst("password-confirm");
LoginFormsProvider loginForms = session.getProvider(LoginFormsProvider.class)
.setUser(user);
if (Validation.isBlank(passwordNew)) {
return loginForms.setError(Messages.MISSING_PASSWORD)
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
} else if (!passwordNew.equals(passwordConfirm)) {
return loginForms.setError(Messages.NOTMATCH_PASSWORD)
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
}
try {
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
} catch (ModelException me) {
return loginForms.setError(me.getMessage(), me.getParameters())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
} catch (Exception ape) {
return loginForms.setError(ape.getMessage())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
}
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
event.event(EventType.UPDATE_PASSWORD).success();
if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
session.sessions().removeClientSession(realm, clientSession);
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED)
.createInfoPage();
}
event = event.clone().event(EventType.LOGIN);
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
}
@Path("email-verification") @Path("email-verification")
@GET @GET
public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) { public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
@ -752,13 +691,11 @@ public class LoginActionsService {
if (key != null) { if (key != null) {
Checks checks = new Checks(); Checks checks = new Checks();
if (!checks.verifyCode(key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) { if (!checks.verifyCode(key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
event.error(Errors.RESET_CREDENTIAL_DISABLED); return checks.response;
return ErrorPage.error(session, Messages.INVALID_CODE);
} }
ClientSessionCode accessCode = checks.clientCode; ClientSessionModel clientSession = checks.clientCode.getClientSession();
return session.getProvider(LoginFormsProvider.class) clientSession.setNote("END_AFTER_REQUIRED_ACTIONS", "true");
.setClientSessionCode(accessCode.getCode()) return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
.createResponse(RequiredAction.UPDATE_PASSWORD);
} else { } else {
event.error(Errors.RESET_CREDENTIAL_DISABLED); event.error(Errors.RESET_CREDENTIAL_DISABLED);
return ErrorPage.error(session, Messages.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_CODE);
@ -843,6 +780,7 @@ public class LoginActionsService {
} }
initEvent(clientSession); initEvent(clientSession);
event.event(EventType.CUSTOM_REQUIRED_ACTION);
RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), factory) { RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), factory) {
@ -865,9 +803,9 @@ public class LoginActionsService {
}; };
provider.processAction(context); provider.processAction(context);
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) { if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION) event.clone().success();
.detail(Details.CUSTOM_REQUIRED_ACTION, action).success();
clientSession.getUserSession().getUser().removeRequiredAction(factory.getId()); clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
event.event(EventType.LOGIN);
return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event); return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
} }
if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) { if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {

View file

@ -854,6 +854,7 @@ public class UsersResource {
accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name()); accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name());
try { try {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
UriBuilder builder = Urls.recoverPasswordBuilder(uriInfo.getBaseUri()); UriBuilder builder = Urls.recoverPasswordBuilder(uriInfo.getBaseUri());
builder.queryParam("key", accessCode.getCode()); builder.queryParam("key", accessCode.getCode());

View file

@ -108,7 +108,7 @@ public class TermsAndConditionsTest {
termsPage.declineTerms(); termsPage.declineTerms();
events.expectLogin().detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID) events.expectLogin().event(EventType.CUSTOM_REQUIRED_ACTION_ERROR).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID)
.error(Errors.REJECTED_BY_USER) .error(Errors.REJECTED_BY_USER)
.removeDetail(Details.CONSENT) .removeDetail(Details.CONSENT)
.assertEvent(); .assertEvent();