KEYCLOAK-1839
This commit is contained in:
parent
2ec143a3ba
commit
59d548228a
2 changed files with 106 additions and 1 deletions
|
@ -24,6 +24,7 @@ package org.keycloak.services.resources;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionContextResult;
|
import org.keycloak.authentication.RequiredActionContextResult;
|
||||||
|
@ -40,6 +41,7 @@ import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
@ -52,10 +54,14 @@ import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.RestartLoginCookie;
|
import org.keycloak.protocol.RestartLoginCookie;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
|
@ -341,10 +347,43 @@ public class LoginActionsService {
|
||||||
return resetCredentials(code, execution);
|
return resetCredentials(code, execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint for executing reset credentials flow. If code is null, a client session is created with the account
|
||||||
|
* service as the client. Successful reset sends you to the account page. Note, account service must be enabled.
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param execution
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@Path(RESET_CREDENTIALS_PATH)
|
@Path(RESET_CREDENTIALS_PATH)
|
||||||
@GET
|
@GET
|
||||||
public Response resetCredentialsGET(@QueryParam("code") String code,
|
public Response resetCredentialsGET(@QueryParam("code") String code,
|
||||||
@QueryParam("execution") String execution) {
|
@QueryParam("execution") String execution) {
|
||||||
|
// we allow applications to link to reset credentials without going through OAuth or SAML handshakes
|
||||||
|
//
|
||||||
|
if (code == null) {
|
||||||
|
if (!realm.isResetPasswordAllowed()) {
|
||||||
|
event.event(EventType.RESET_PASSWORD);
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
|
||||||
|
|
||||||
|
}
|
||||||
|
// set up the account service as the endpoint to call.
|
||||||
|
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||||
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||||
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||||
|
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
||||||
|
//clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
|
||||||
|
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
|
||||||
|
clientSession.setRedirectUri(redirectUri);
|
||||||
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||||
|
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
||||||
|
clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
|
||||||
|
clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
|
||||||
|
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||||
|
return processResetCredentials(null, clientSession, null);
|
||||||
|
}
|
||||||
return resetCredentials(code, execution);
|
return resetCredentials(code, execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,6 +396,13 @@ public class LoginActionsService {
|
||||||
final ClientSessionCode clientCode = checks.clientCode;
|
final ClientSessionCode clientCode = checks.clientCode;
|
||||||
final ClientSessionModel clientSession = clientCode.getClientSession();
|
final ClientSessionModel clientSession = clientCode.getClientSession();
|
||||||
|
|
||||||
|
if (!realm.isResetPasswordAllowed()) {
|
||||||
|
event.client(clientCode.getClientSession().getClient());
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return processResetCredentials(execution, clientSession, null);
|
return processResetCredentials(execution, clientSession, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +619,7 @@ public class LoginActionsService {
|
||||||
return checks.response;
|
return checks.response;
|
||||||
}
|
}
|
||||||
ClientSessionModel clientSession = checks.clientCode.getClientSession();
|
ClientSessionModel clientSession = checks.clientCode.getClientSession();
|
||||||
clientSession.setNote("END_AFTER_REQUIRED_ACTIONS", "true");
|
clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
|
||||||
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
|
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
|
||||||
return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
|
return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.Constants;
|
||||||
import org.keycloak.testsuite.MailUtil;
|
import org.keycloak.testsuite.MailUtil;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
@ -140,6 +141,64 @@ public class ResetPasswordTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resetPasswordLink() throws IOException, MessagingException {
|
||||||
|
String username = "login-test";
|
||||||
|
String resetUri = Constants.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
|
||||||
|
driver.navigate().to(resetUri);
|
||||||
|
|
||||||
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
|
resetPasswordPage.changePassword(username);
|
||||||
|
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
|
||||||
|
.user(userId)
|
||||||
|
.detail(Details.REDIRECT_URI, Constants.AUTH_SERVER_ROOT + "/realms/test/account/")
|
||||||
|
.client("account")
|
||||||
|
.detail(Details.USERNAME, username)
|
||||||
|
.detail(Details.EMAIL, "login@test.com")
|
||||||
|
.session((String)null)
|
||||||
|
.assertEvent();
|
||||||
|
|
||||||
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
String changePasswordUrl = getPasswordResetEmailLink(message);
|
||||||
|
|
||||||
|
driver.navigate().to(changePasswordUrl.trim());
|
||||||
|
|
||||||
|
updatePasswordPage.assertCurrent();
|
||||||
|
|
||||||
|
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
||||||
|
|
||||||
|
String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD)
|
||||||
|
.detail(Details.REDIRECT_URI, Constants.AUTH_SERVER_ROOT + "/realms/test/account/")
|
||||||
|
.client("account")
|
||||||
|
.user(userId).detail(Details.USERNAME, username).assertEvent().getSessionId();
|
||||||
|
|
||||||
|
events.expectLogin().user(userId).detail(Details.USERNAME, username)
|
||||||
|
.detail(Details.REDIRECT_URI, Constants.AUTH_SERVER_ROOT + "/realms/test/account/")
|
||||||
|
.client("account")
|
||||||
|
.session(sessionId).assertEvent();
|
||||||
|
|
||||||
|
oauth.openLogout();
|
||||||
|
|
||||||
|
events.expectLogout(sessionId).user(userId).session(sessionId).assertEvent();
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
|
||||||
|
loginPage.login("login-test", "resetPassword");
|
||||||
|
|
||||||
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
|
|
||||||
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resetPassword() throws IOException, MessagingException {
|
public void resetPassword() throws IOException, MessagingException {
|
||||||
resetPassword("login-test");
|
resetPassword("login-test");
|
||||||
|
|
Loading…
Reference in a new issue