KEYCLOAK-1014 Don't redirect to app after reset password or verify email if new browser session
This commit is contained in:
parent
ad799058a6
commit
6c7f35c509
12 changed files with 264 additions and 36 deletions
15
forms/common-themes/src/main/resources/theme/login/base/info.ftl
Executable file
15
forms/common-themes/src/main/resources/theme/login/base/info.ftl
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.registrationLayout displayMessage=false; section>
|
||||||
|
<#if section = "title">
|
||||||
|
${message.summary}
|
||||||
|
<#elseif section = "header">
|
||||||
|
${message.summary}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div id="kc-info-message">
|
||||||
|
<p class="instruction">${message.summary}</p>
|
||||||
|
<#if client.baseUrl??>
|
||||||
|
<p><a href="${client.baseUrl}">${rb.backToApplication}</a></p>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -20,6 +20,7 @@ rememberMe=Remember me
|
||||||
passwordConfirm=Confirm password
|
passwordConfirm=Confirm password
|
||||||
passwordNew=New Password
|
passwordNew=New Password
|
||||||
passwordNewConfirm=New Password confirmation
|
passwordNewConfirm=New Password confirmation
|
||||||
|
passwordUpdated=Password updated
|
||||||
cancel=Cancel
|
cancel=Cancel
|
||||||
accept=Accept
|
accept=Accept
|
||||||
submit=Submit
|
submit=Submit
|
||||||
|
@ -85,6 +86,7 @@ emailVerifyInstr=An email with instructions to verify your email address has bee
|
||||||
emailVerifyInstrQ=Haven't received a verification code in your email?
|
emailVerifyInstrQ=Haven't received a verification code in your email?
|
||||||
emailVerifyClick=Click here
|
emailVerifyClick=Click here
|
||||||
emailVerifyResend=to re-send the email.
|
emailVerifyResend=to re-send the email.
|
||||||
|
emailVerified=Email verified
|
||||||
|
|
||||||
error=A system error has occured, contact admin
|
error=A system error has occured, contact admin
|
||||||
errorTitle=We're sorry...
|
errorTitle=We're sorry...
|
||||||
|
@ -106,6 +108,7 @@ errorHeader=Error!
|
||||||
|
|
||||||
emailForgotHeader=Forgot Your Password?
|
emailForgotHeader=Forgot Your Password?
|
||||||
backToLogin=« Back to Login
|
backToLogin=« Back to Login
|
||||||
|
backToApplication=« Back to Application
|
||||||
emailUpdateHeader=Update password
|
emailUpdateHeader=Update password
|
||||||
emailSent=You should receive an email shortly with further instructions.
|
emailSent=You should receive an email shortly with further instructions.
|
||||||
emailSendError=Failed to send email, please try again later
|
emailSendError=Failed to send email, please try again later
|
||||||
|
|
|
@ -5,6 +5,6 @@ package org.keycloak.login;
|
||||||
*/
|
*/
|
||||||
public enum LoginFormsPages {
|
public enum LoginFormsPages {
|
||||||
|
|
||||||
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, ERROR, LOGIN_UPDATE_PROFILE, CODE;
|
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ public interface LoginFormsProvider extends Provider {
|
||||||
|
|
||||||
public Response createRegistration();
|
public Response createRegistration();
|
||||||
|
|
||||||
|
public Response createInfoPage();
|
||||||
|
|
||||||
public Response createErrorPage();
|
public Response createErrorPage();
|
||||||
|
|
||||||
public Response createOAuthGrant();
|
public Response createOAuthGrant();
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
@ -51,7 +52,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
|
private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
|
||||||
|
|
||||||
private String message;
|
public static enum MessageType {SUCCESS, WARNING, ERROR}
|
||||||
|
|
||||||
private String accessCode;
|
private String accessCode;
|
||||||
private Response.Status status;
|
private Response.Status status;
|
||||||
private List<RoleModel> realmRolesRequested;
|
private List<RoleModel> realmRolesRequested;
|
||||||
|
@ -61,8 +63,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
private String accessRequestMessage;
|
private String accessRequestMessage;
|
||||||
private URI actionUri;
|
private URI actionUri;
|
||||||
|
|
||||||
public static enum MessageType {SUCCESS, WARNING, ERROR}
|
private String message;
|
||||||
|
|
||||||
private MessageType messageType = MessageType.ERROR;
|
private MessageType messageType = MessageType.ERROR;
|
||||||
|
|
||||||
private MultivaluedMap<String, String> formData;
|
private MultivaluedMap<String, String> formData;
|
||||||
|
@ -253,6 +254,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
return createResponse(LoginFormsPages.REGISTER);
|
return createResponse(LoginFormsPages.REGISTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Response createInfoPage() {
|
||||||
|
return createResponse(LoginFormsPages.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
public Response createErrorPage() {
|
public Response createErrorPage() {
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
status = Response.Status.INTERNAL_SERVER_ERROR;
|
status = Response.Status.INTERNAL_SERVER_ERROR;
|
||||||
|
|
|
@ -25,6 +25,8 @@ public class Templates {
|
||||||
return "login-update-password.ftl";
|
return "login-update-password.ftl";
|
||||||
case REGISTER:
|
case REGISTER:
|
||||||
return "register.ftl";
|
return "register.ftl";
|
||||||
|
case INFO:
|
||||||
|
return "info.ftl";
|
||||||
case ERROR:
|
case ERROR:
|
||||||
return "error.ftl";
|
return "error.ftl";
|
||||||
case LOGIN_UPDATE_PROFILE:
|
case LOGIN_UPDATE_PROFILE:
|
||||||
|
|
|
@ -26,4 +26,12 @@ public class ClientBean {
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
return client.getClientId();
|
return client.getClientId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
if (client instanceof ApplicationModel) {
|
||||||
|
return ((ApplicationModel) client).getBaseUrl();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.protocol.LoginProtocol;
|
||||||
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.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
|
@ -366,6 +367,7 @@ public class AuthenticationManager {
|
||||||
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
|
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
|
||||||
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
|
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
|
||||||
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
||||||
|
LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return loginFormsProvider
|
return loginFormsProvider
|
||||||
|
|
|
@ -53,6 +53,7 @@ import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -61,6 +62,7 @@ import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -79,6 +81,8 @@ public class LoginActionsService {
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(LoginActionsService.class);
|
protected static final Logger logger = Logger.getLogger(LoginActionsService.class);
|
||||||
|
|
||||||
|
public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
|
||||||
|
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
|
@ -163,6 +167,18 @@ public class LoginActionsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) {
|
||||||
|
if (!check(code)) {
|
||||||
|
return false;
|
||||||
|
} else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
|
||||||
|
event.error(Errors.INVALID_CODE);
|
||||||
|
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean check(String code) {
|
public boolean check(String code) {
|
||||||
if (!checkSsl()) {
|
if (!checkSsl()) {
|
||||||
event.error(Errors.SSL_REQUIRED);
|
event.error(Errors.SSL_REQUIRED);
|
||||||
|
@ -690,7 +706,7 @@ public class LoginActionsService {
|
||||||
final MultivaluedMap<String, String> formData) {
|
final MultivaluedMap<String, String> formData) {
|
||||||
event.event(EventType.UPDATE_PASSWORD);
|
event.event(EventType.UPDATE_PASSWORD);
|
||||||
Checks checks = new Checks();
|
Checks checks = new Checks();
|
||||||
if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD)) {
|
if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) {
|
||||||
return checks.response;
|
return checks.response;
|
||||||
}
|
}
|
||||||
ClientSessionCode accessCode = checks.clientCode;
|
ClientSessionCode accessCode = checks.clientCode;
|
||||||
|
@ -724,7 +740,17 @@ public class LoginActionsService {
|
||||||
|
|
||||||
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
||||||
|
|
||||||
event.clone().event(EventType.UPDATE_PASSWORD).success();
|
event.event(EventType.UPDATE_PASSWORD).success();
|
||||||
|
|
||||||
|
if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
|
||||||
|
String actionCookieValue = getActionCookie();
|
||||||
|
if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
|
||||||
|
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("passwordUpdated").createInfoPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = event.clone().event(EventType.LOGIN);
|
||||||
|
|
||||||
return redirectOauth(user, accessCode, clientSession, userSession);
|
return redirectOauth(user, accessCode, clientSession, userSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,7 +773,14 @@ public class LoginActionsService {
|
||||||
|
|
||||||
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
|
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
|
||||||
|
|
||||||
event.clone().event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
||||||
|
|
||||||
|
String actionCookieValue = getActionCookie();
|
||||||
|
if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
|
||||||
|
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("emailVerified").createInfoPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
|
||||||
|
|
||||||
return redirectOauth(user, accessCode, clientSession, userSession);
|
return redirectOauth(user, accessCode, clientSession, userSession);
|
||||||
} else {
|
} else {
|
||||||
|
@ -760,6 +793,8 @@ public class LoginActionsService {
|
||||||
UserSessionModel userSession = clientSession.getUserSession();
|
UserSessionModel userSession = clientSession.getUserSession();
|
||||||
initEvent(clientSession);
|
initEvent(clientSession);
|
||||||
|
|
||||||
|
createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
|
||||||
|
|
||||||
return Flows.forms(session, realm, null, uriInfo)
|
return Flows.forms(session, realm, null, uriInfo)
|
||||||
.setClientSessionCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.setUser(userSession.getUser())
|
.setUser(userSession.getUser())
|
||||||
|
@ -777,7 +812,6 @@ public class LoginActionsService {
|
||||||
return checks.response;
|
return checks.response;
|
||||||
}
|
}
|
||||||
ClientSessionCode accessCode = checks.clientCode;
|
ClientSessionCode accessCode = checks.clientCode;
|
||||||
accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
|
||||||
return Flows.forms(session, realm, null, uriInfo)
|
return Flows.forms(session, realm, null, uriInfo)
|
||||||
.setClientSessionCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
|
@ -864,11 +898,23 @@ public class LoginActionsService {
|
||||||
.setClientSessionCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createErrorPage();
|
.createErrorPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Flows.forms(session, realm, client, uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset();
|
return Flows.forms(session, realm, client, uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getActionCookie() {
|
||||||
|
Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
|
||||||
|
AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
|
||||||
|
return cookie != null ? cookie.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
|
||||||
|
CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
|
||||||
|
}
|
||||||
|
|
||||||
private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
|
private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ 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;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
|
import org.keycloak.testsuite.pages.InfoPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.RegisterPage;
|
import org.keycloak.testsuite.pages.RegisterPage;
|
||||||
import org.keycloak.testsuite.pages.VerifyEmailPage;
|
import org.keycloak.testsuite.pages.VerifyEmailPage;
|
||||||
|
@ -51,6 +52,9 @@ import javax.mail.MessagingException;
|
||||||
import javax.mail.internet.MimeMessage;
|
import javax.mail.internet.MimeMessage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
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>
|
||||||
*/
|
*/
|
||||||
|
@ -86,6 +90,9 @@ public class RequiredActionEmailVerificationTest {
|
||||||
@WebResource
|
@WebResource
|
||||||
protected RegisterPage registerPage;
|
protected RegisterPage registerPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected InfoPage infoPage;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
oauth.state("mystate"); // have to set this as keycloak validates that state is sent
|
oauth.state("mystate"); // have to set this as keycloak validates that state is sent
|
||||||
|
@ -200,4 +207,41 @@ public class RequiredActionEmailVerificationTest {
|
||||||
events.expectLogin().session(sessionId).assertEvent();
|
events.expectLogin().session(sessionId).assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyEmailNewBrowserSession() throws IOException, MessagingException {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
Assert.assertTrue(verifyEmailPage.isCurrent());
|
||||||
|
|
||||||
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
String body = (String) message.getContent();
|
||||||
|
String verificationUrl = MailUtil.getLink(body);
|
||||||
|
|
||||||
|
AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
|
||||||
|
Event sendEvent = emailEvent.assertEvent();
|
||||||
|
String sessionId = sendEvent.getSessionId();
|
||||||
|
|
||||||
|
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
|
Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1].split("\\.")[1]);
|
||||||
|
|
||||||
|
driver.manage().deleteAllCookies();
|
||||||
|
|
||||||
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||||
|
|
||||||
|
assertTrue(infoPage.isCurrent());
|
||||||
|
assertEquals("Email verified", infoPage.getInfo());
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
|
||||||
|
assertTrue(loginPage.isCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.forms;
|
package org.keycloak.testsuite.forms;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -41,6 +40,7 @@ import org.keycloak.testsuite.OAuthClient;
|
||||||
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;
|
||||||
|
import org.keycloak.testsuite.pages.InfoPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordResetPage;
|
import org.keycloak.testsuite.pages.LoginPasswordResetPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
|
@ -56,6 +56,9 @@ import javax.mail.internet.MimeMessage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
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>
|
||||||
*/
|
*/
|
||||||
|
@ -103,6 +106,9 @@ public class ResetPasswordTest {
|
||||||
@WebResource
|
@WebResource
|
||||||
protected ErrorPage errorPage;
|
protected ErrorPage errorPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected InfoPage infoPage;
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected LoginPasswordResetPage resetPasswordPage;
|
protected LoginPasswordResetPage resetPasswordPage;
|
||||||
|
|
||||||
|
@ -132,13 +138,13 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
resetPasswordPage.backToLogin();
|
resetPasswordPage.backToLogin();
|
||||||
|
|
||||||
Assert.assertTrue(loginPage.isCurrent());
|
assertTrue(loginPage.isCurrent());
|
||||||
|
|
||||||
loginPage.login("login-test", "password");
|
loginPage.login("login-test", "password");
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
@ -149,8 +155,8 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
events.expect(EventType.RESET_PASSWORD_ERROR).client((String) null).user((String) null).error("invalid_code").clearDetails().assertEvent();
|
events.expect(EventType.RESET_PASSWORD_ERROR).client((String) null).user((String) null).error("invalid_code").clearDetails().assertEvent();
|
||||||
|
|
||||||
Assert.assertTrue(errorPage.isCurrent());
|
assertTrue(errorPage.isCurrent());
|
||||||
Assert.assertEquals("Unknown code, please login again through your application.", errorPage.getError());
|
assertEquals("Unknown code, please login again through your application.", errorPage.getError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -168,7 +174,7 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
resetPasswordPage.backToLogin();
|
resetPasswordPage.backToLogin();
|
||||||
|
|
||||||
Assert.assertTrue(loginPage.isCurrent());
|
assertTrue(loginPage.isCurrent());
|
||||||
|
|
||||||
loginPage.login("login@test.com", "password");
|
loginPage.login("login@test.com", "password");
|
||||||
|
|
||||||
|
@ -177,8 +183,8 @@ public class ResetPasswordTest {
|
||||||
String code = oauth.getCurrentQuery().get("code");
|
String code = oauth.getCurrentQuery().get("code");
|
||||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
|
||||||
Assert.assertEquals(200, tokenResponse.getStatusCode());
|
assertEquals(200, tokenResponse.getStatusCode());
|
||||||
Assert.assertEquals(userId, oauth.verifyToken(tokenResponse.getAccessToken()).getSubject());
|
assertEquals(userId, oauth.verifyToken(tokenResponse.getAccessToken()).getSubject());
|
||||||
|
|
||||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).user(userId).assertEvent();
|
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).user(userId).assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -200,9 +206,9 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
|
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
|
||||||
|
|
||||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
@ -217,7 +223,7 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, username).session(sessionId).assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, username).session(sessionId).assertEvent();
|
||||||
|
|
||||||
|
@ -231,7 +237,7 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -245,11 +251,11 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
resetPasswordPage.assertCurrent();
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
|
assertEquals(0, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
|
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -268,9 +274,9 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
|
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
|
||||||
|
|
||||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
@ -283,7 +289,7 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
errorPage.assertCurrent();
|
errorPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertEquals("Invalid code, please login again through your application.", errorPage.getError());
|
assertEquals("Invalid code, please login again through your application.", errorPage.getError());
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.RESET_PASSWORD).error("invalid_code").client((String) null).user((String) null).session((String) null).clearDetails().assertEvent();
|
events.expectRequiredAction(EventType.RESET_PASSWORD).error("invalid_code").client((String) null).user((String) null).session((String) null).clearDetails().assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -310,11 +316,11 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
resetPasswordPage.assertCurrent();
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
|
assertEquals(0, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("user_disabled").assertEvent();
|
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("user_disabled").assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -350,11 +356,11 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
resetPasswordPage.assertCurrent();
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
|
assertEquals(0, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("invalid_email").assertEvent();
|
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("invalid_email").assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -388,11 +394,11 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
errorPage.assertCurrent();
|
errorPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertEquals("Failed to send email, please try again later", errorPage.getError());
|
assertEquals("Failed to send email, please try again later", errorPage.getError());
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
|
assertEquals(0, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error(Errors.EMAIL_SEND_FAILED).assertEvent();
|
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD_ERROR).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error(Errors.EMAIL_SEND_FAILED).assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -423,9 +429,9 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
resetPasswordPage.assertCurrent();
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
@ -440,13 +446,13 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
updatePasswordPage.changePassword("invalid", "invalid");
|
updatePasswordPage.changePassword("invalid", "invalid");
|
||||||
|
|
||||||
Assert.assertEquals("Invalid password: minimum length 8", resetPasswordPage.getErrorMessage());
|
assertEquals("Invalid password: minimum length 8", resetPasswordPage.getErrorMessage());
|
||||||
|
|
||||||
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
|
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").session(sessionId).assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").session(sessionId).assertEvent();
|
||||||
|
|
||||||
|
@ -458,9 +464,51 @@ public class ResetPasswordTest {
|
||||||
|
|
||||||
loginPage.login("login-test", "resetPasswordWithPasswordPolicy");
|
loginPage.login("login-test", "resetPasswordWithPasswordPolicy");
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resetPasswordNewBrowserSession() throws IOException, MessagingException {
|
||||||
|
String username = "login-test";
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.resetPassword();
|
||||||
|
|
||||||
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
|
resetPasswordPage.changePassword(username);
|
||||||
|
|
||||||
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
|
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
|
||||||
|
|
||||||
|
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||||
|
|
||||||
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
|
String body = (String) message.getContent();
|
||||||
|
String changePasswordUrl = MailUtil.getLink(body);
|
||||||
|
|
||||||
|
driver.manage().deleteAllCookies();
|
||||||
|
|
||||||
|
driver.navigate().to(changePasswordUrl.trim());
|
||||||
|
|
||||||
|
updatePasswordPage.assertCurrent();
|
||||||
|
|
||||||
|
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
|
||||||
|
|
||||||
|
assertTrue(infoPage.isCurrent());
|
||||||
|
assertEquals("Password updated", infoPage.getInfo());
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
|
||||||
|
assertTrue(loginPage.isCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source.
|
||||||
|
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||||
|
* as indicated by the @author tags. See the copyright.txt file in the
|
||||||
|
* distribution for a full listing of individual contributors.
|
||||||
|
*
|
||||||
|
* This is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this software; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||||
|
*/
|
||||||
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class InfoPage extends AbstractPage {
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
@FindBy(className = "instruction")
|
||||||
|
private WebElement infoMessage;
|
||||||
|
|
||||||
|
public String getInfo() {
|
||||||
|
return infoMessage.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCurrent() {
|
||||||
|
return driver.getPageSource().contains("kc-info-message");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue