reset password refactor

This commit is contained in:
Bill Burke 2015-08-16 15:20:16 -04:00
parent c0f3d851db
commit c7b5975ac1
13 changed files with 237 additions and 222 deletions

View file

@ -16,14 +16,9 @@
</div>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">
<span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
</div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-submit" type="submit" value="${msg("doLogIn")}"/>
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
</div>
</div>
</form>

View file

@ -80,7 +80,8 @@ public interface ClientSessionModel {
RECOVER_PASSWORD,
AUTHENTICATE,
SOCIAL_CALLBACK,
LOGGED_OUT
LOGGED_OUT,
RESET_CREDENTIALS
}
public enum ExecutionStatus {

View file

@ -224,4 +224,11 @@ public interface AuthenticationFlowContext {
*
*/
void cancelLogin();
/**
* Abort the current flow and restart it using the realm's browser login
*
* @return
*/
void resetBrowserLogin();
}

View file

@ -16,5 +16,6 @@ public enum AuthenticationFlowError {
USER_CONFLICT,
USER_TEMPORARILY_DISABLED,
INTERNAL_ERROR,
UNKNOWN_USER
UNKNOWN_USER,
RESET_TO_BROWSER_LOGIN
}

View file

@ -391,6 +391,11 @@ public class AuthenticationProcessor {
Response response = protocol.cancelLogin(getClientSession());
forceChallenge(response);
}
@Override
public void resetBrowserLogin() {
this.status = FlowStatus.RESET_BROWSER_LOGIN;
}
}
public void logFailure() {
@ -434,6 +439,21 @@ public class AuthenticationProcessor {
event.error(Errors.EXPIRED_CODE);
return ErrorPage.error(session, Messages.EXPIRED_CODE);
} else if (e.getError() == AuthenticationFlowError.RESET_TO_BROWSER_LOGIN) {
resetFlow(getClientSession());
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
.setFlowId(realm.getBrowserFlow().getId())
.setConnection(connection)
.setEventBuilder(event)
.setProtector(protector)
.setRealm(realm)
.setSession(session)
.setUriInfo(uriInfo)
.setRequest(request);
return processor.authenticate();
} else {
event.error(Errors.INVALID_USER_CREDENTIALS);
return ErrorPage.error(session, Messages.INVALID_USER);
@ -530,10 +550,11 @@ public class AuthenticationProcessor {
public void checkClientSession() {
ClientSessionCode code = new ClientSessionCode(realm, clientSession);
if (!code.isValidAction(ClientSessionModel.Action.AUTHENTICATE.name())) {
String action = ClientSessionModel.Action.AUTHENTICATE.name();
if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
}
if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) {
if (!code.isActionActive(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.EXPIRED_CODE);
}
clientSession.setTimestamp(Time.currentTime());
@ -564,12 +585,16 @@ public class AuthenticationProcessor {
String username = clientSession.getAuthenticatedUser().getUsername();
String attemptedUsername = clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME);
if (attemptedUsername != null) username = attemptedUsername;
String rememberMe = clientSession.getNote(Details.REMEMBER_ME);
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
if (userSession == null) { // if no authenticator attached a usersession
boolean remember = "true".equals(clientSession.getNote(Details.REMEMBER_ME));
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", remember, null, null);
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), clientSession.getAuthMethod(), remember, null, null);
userSession.setState(UserSessionModel.State.LOGGING_IN);
userSessionCreated = true;
}
if (remember) {
event.detail(Details.REMEMBER_ME, "true");
}
TokenManager.attachClientSession(userSession, clientSession);
event.user(userSession.getUser())
.detail(Details.USERNAME, username)
@ -598,21 +623,7 @@ public class AuthenticationProcessor {
}
protected Response authenticationComplete() {
String username = clientSession.getAuthenticatedUser().getUsername();
String rememberMe = clientSession.getNote(Details.REMEMBER_ME);
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
if (userSession == null) { // if no authenticator attached a usersession
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), clientSession.getAuthMethod(), remember, null, null);
userSession.setState(UserSessionModel.State.LOGGING_IN);
}
if (remember) {
event.detail(Details.REMEMBER_ME, "true");
}
TokenManager.attachClientSession(userSession, clientSession);
event.user(userSession.getUser())
.detail(Details.USERNAME, username)
.session(userSession);
attachSession();
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
}

View file

@ -166,6 +166,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
return sendChallenge(result, execution);
}
throw new AuthenticationFlowException(result.getError());
} else if (status == FlowStatus.RESET_BROWSER_LOGIN) {
AuthenticationProcessor.logger.debugv("reset browser login from authenticator: {0}", execution.getAuthenticator());
throw new AuthenticationFlowException(AuthenticationFlowError.RESET_TO_BROWSER_LOGIN);
} else if (status == FlowStatus.FORCE_CHALLENGE) {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);

View file

@ -42,6 +42,12 @@ public enum FlowStatus {
* a Kerberos authenticator did not see a negotiate header. There was no error, but the execution was attempted.
*
*/
ATTEMPTED
ATTEMPTED,
/**
* Aborting this flow and starting the realm's browser flow from the beginning
*
*/
RESET_BROWSER_LOGIN
}

View file

@ -5,6 +5,7 @@ import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.events.Details;
@ -62,34 +63,22 @@ public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFa
user = context.getSession().users().getUserByEmail(username, context.getRealm());
}
context.getClientSession().setNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
// we don't want people guessing usernames, so if there is a problem, just continue, but don't set the user
// a null user will notify further executions, that this was a failure.
if (user == null) {
event.error(Errors.INVALID_USER_CREDENTIALS);
Response challenge = context.form()
.setError(Messages.INVALID_USER)
.createPasswordReset();
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
return;
event.clone()
.detail(Details.USERNAME, username)
.error(Errors.USER_NOT_FOUND);
} else if (!user.isEnabled()) {
event.clone()
.detail(Details.USERNAME, username)
.user(user).error(Errors.USER_DISABLED);
} else {
context.setUser(user);
}
if (!user.isEnabled()) {
event.user(user).error(Errors.USER_DISABLED);
Response challenge = context.form()
.setError(Messages.ACCOUNT_DISABLED)
.createPasswordReset();
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
return;
}
if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
event.user(user).error(Errors.INVALID_EMAIL);
Response challenge = context.form()
.setError(Messages.INVALID_EMAIL)
.createPasswordReset();
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
return;
}
context.setUser(user);
context.success();
}

View file

@ -7,11 +7,13 @@ import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.login.LoginFormsProvider;
@ -49,13 +51,29 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
@Override
public void authenticate(AuthenticationFlowContext context) {
UserModel user = context.getUser();
EventBuilder event = context.getEvent();
if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
event.user(user).error(Errors.INVALID_EMAIL);
String username = context.getClientSession().getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME);
// we don't want people guessing usernames, so if there was a problem obtaining the user, the user will be null.
// just redisplay this form
if (user == null) {
Response challenge = context.form()
.setError(Messages.INVALID_EMAIL)
.createPasswordReset();
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge);
.setSuccess(Messages.EMAIL_SENT)
.createForm("validate-reset-email.ftl");
context.challenge(challenge);
return;
}
EventBuilder event = context.getEvent();
// we don't want people guessing usernames, so if there is a problem, just continuously challenge
if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
event.user(user)
.detail(Details.USERNAME, username)
.error(Errors.INVALID_EMAIL);
Response challenge = context.form()
.setSuccess(Messages.EMAIL_SENT)
.createForm("validate-reset-email.ftl");
context.challenge(challenge);
return;
}
@ -68,14 +86,19 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
try {
context.getSession().getProvider(EmailProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(secret, link, expiration);
event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
event.clone().event(EventType.SEND_RESET_PASSWORD)
.user(user)
.detail(Details.USERNAME, username)
.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
Response challenge = context.form()
.setSuccess(Messages.EMAIL_SENT)
.createForm("validate-reset-email.ftl");
context.challenge(challenge);
} catch (EmailException e) {
event.error(Errors.EMAIL_SEND_FAILED);
event.clone().event(EventType.SEND_RESET_PASSWORD)
.detail(Details.USERNAME, username)
.user(user)
.error(Errors.EMAIL_SEND_FAILED);
logger.error("Failed to send password reset email", e);
Response challenge = context.form()
.setError(Messages.EMAIL_SENT_ERROR)
@ -92,7 +115,13 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
key =context.getUriInfo().getQueryParameters().getFirst(KEY);
} else if (context.getHttpRequest().getHttpMethod().equalsIgnoreCase("POST")) {
key = context.getHttpRequest().getDecodedFormParameters().getFirst(KEY);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("cancel")) {
context.resetBrowserLogin();
return;
}
key = formData.getFirst(KEY);
}
// Can only guess once! We remove the note so another guess can't happen
@ -110,7 +139,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
@Override
public boolean requiresUser() {
return true;
return false;
}
@Override

View file

@ -29,6 +29,7 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionContextResult;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.events.Details;
@ -762,85 +763,6 @@ public class LoginActionsService {
}
}
private Response sendPasswordReset(@QueryParam("code") String code,
final MultivaluedMap<String, String> formData) {
event.event(EventType.SEND_RESET_PASSWORD);
if (!realm.isResetPasswordAllowed()) {
event.error(Errors.RESET_CREDENTIAL_DISABLED);
return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
}
Checks checks = new Checks();
if (!checks.verifyCode(code)) {
return checks.response;
}
final ClientSessionCode accessCode = checks.clientCode;
final ClientSessionModel clientSession = accessCode.getClientSession();
ClientModel client = clientSession.getClient();
String username = formData.getFirst("username");
if (username == null || username.isEmpty()) {
event.error(Errors.USERNAME_MISSING);
return session.getProvider(LoginFormsProvider.class)
.setError(Messages.MISSING_USERNAME)
.setClientSessionCode(accessCode.getCode())
.createPasswordReset();
}
event.client(client.getClientId())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, "form")
.detail(Details.USERNAME, username);
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null && username.contains("@")) {
user = session.users().getUserByEmail(username, realm);
}
if (user == null) {
event.error(Errors.USER_NOT_FOUND);
} else if (!user.isEnabled()) {
event.user(user).error(Errors.USER_DISABLED);
} else if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
event.user(user).error(Errors.INVALID_EMAIL);
} else {
event.user(user);
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
event.session(userSession);
TokenManager.attachClientSession(userSession, clientSession);
accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name());
try {
UriBuilder builder = Urls.loginResetCredentialsBuilder(uriInfo.getBaseUri());
builder.queryParam("key", accessCode.getCode());
String link = builder.build(realm.getName()).toString();
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendChangePassword(link, expiration);
event.detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
} catch (EmailException e) {
event.error(Errors.EMAIL_SEND_FAILED);
logger.error("Failed to send password reset email", e);
return session.getProvider(LoginFormsProvider.class)
.setError(Messages.EMAIL_SENT_ERROR)
.setClientSessionCode(accessCode.getCode())
.createErrorPage();
}
createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
}
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.EMAIL_SENT)
.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);
@ -857,6 +779,7 @@ public class LoginActionsService {
.session(clientSession.getUserSession().getId())
.detail(Details.CODE_ID, clientSession.getId())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
.detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
.detail(Details.RESPONSE_TYPE, "code");
UserSessionModel userSession = clientSession.getUserSession();

View file

@ -167,7 +167,7 @@ public class AccountTest {
});
}
@Test
//@Test
public void ideTesting() throws Exception {
Thread.sleep(100000000);
}

View file

@ -44,6 +44,7 @@ import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginPasswordResetPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.ValidatePassworrdEmailResetPage;
import org.keycloak.testsuite.rule.GreenMailRule;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
@ -65,6 +66,7 @@ import static org.junit.Assert.*;
*/
public class ResetPasswordTest {
static int lifespan = 0;
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule((new KeycloakRule.KeycloakSetup() {
@Override
@ -81,6 +83,7 @@ public class ResetPasswordTest {
user.updateCredential(creds);
appRealm.setEventsListeners(Collections.singleton("dummy"));
lifespan = appRealm.getAccessCodeLifespanUserAction();
}
}));
@ -113,6 +116,9 @@ public class ResetPasswordTest {
@WebResource
protected LoginPasswordResetPage resetPasswordPage;
@WebResource
protected ValidatePassworrdEmailResetPage validateResetPage;
@WebResource
protected LoginPasswordUpdatePage updatePasswordPage;
@ -133,12 +139,13 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("login-test");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.session((String)null)
.user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
String src = driver.getPageSource();
resetPasswordPage.backToLogin();
validateResetPage.cancel();
assertTrue(loginPage.isCurrent());
@ -169,17 +176,19 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("test-user@localhost");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, "test-user@localhost").detail(Details.EMAIL, "test-user@localhost").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, "test-user@localhost")
.session((String) null)
.detail(Details.EMAIL, "test-user@localhost").assertEvent();
resetPasswordPage.backToLogin();
validateResetPage.cancel();
assertTrue(loginPage.isCurrent());
loginPage.login("login@test.com", "password");
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
String code = oauth.getCurrentQuery().get("code");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
@ -203,9 +212,14 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword(username);
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.user(userId)
.detail(Details.USERNAME, username)
.detail(Details.EMAIL, "login@test.com")
.session((String)null)
.assertEvent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@ -221,7 +235,7 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword("resetPassword", "resetPassword");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, username).assertEvent().getSessionId();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -248,10 +262,10 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword(username);
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId)
.detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).session((String)null)
.detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@ -265,12 +279,12 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword(password, password);
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId)
.detail(Details.USERNAME, username).assertEvent();
String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId)
.detail(Details.USERNAME, username).assertEvent().getSessionId();
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).assertEvent();
oauth.openLogout();
@ -285,10 +299,10 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword(username);
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId)
.detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).session((String)null)
.detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@ -315,13 +329,13 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("invalid");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
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.RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
}
@Test
@ -339,7 +353,7 @@ public class ResetPasswordTest {
assertEquals(0, greenMail.getReceivedMessages().length);
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).client((String) null).user((String) null).session((String) null).clearDetails().error("username_missing").assertEvent();
events.expectRequiredAction(EventType.RESET_PASSWORD).user((String) null).session((String) null).clearDetails().error("username_missing").assertEvent();
}
@ -353,9 +367,11 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("login-test");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.session((String)null)
.user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@ -365,13 +381,13 @@ public class ResetPasswordTest {
String changePasswordUrl = getPasswordResetEmailLink(message);
Time.setOffset(350);
Time.setOffset(1800+23);
driver.navigate().to(changePasswordUrl.trim());
errorPage.assertCurrent();
loginPage.assertCurrent();
assertEquals("Login timeout. Please login again.", errorPage.getError());
assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
events.expectRequiredAction(EventType.RESET_PASSWORD).error("expired_code").client("test-app").user((String) null).session((String) null).clearDetails().assertEvent();
} finally {
@ -396,13 +412,13 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("login-test");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
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.RESET_PASSWORD).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("user_disabled").assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@ -434,13 +450,13 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("login-test");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
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.RESET_PASSWORD_ERROR).session((String) null).user(userId).detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error("invalid_email").assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@ -476,7 +492,9 @@ public class ResetPasswordTest {
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)
.session((String)null)
.detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).error(Errors.EMAIL_SEND_FAILED).assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@ -503,7 +521,7 @@ public class ResetPasswordTest {
resetPasswordPage.changePassword("login-test");
resetPasswordPage.assertCurrent();
validateResetPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@ -513,7 +531,7 @@ public class ResetPasswordTest {
String changePasswordUrl = getPasswordResetEmailLink(message);
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).session((String)null).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
driver.navigate().to(changePasswordUrl.trim());
@ -525,7 +543,7 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -585,47 +603,6 @@ public class ResetPasswordTest {
}
}
@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 changePasswordUrl = getPasswordResetEmailLink(message);
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("Your password has been updated.", infoPage.getInfo());
loginPage.open();
assertTrue(loginPage.isCurrent());
}
private String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
Multipart multipart = (Multipart) message.getContent();

View file

@ -0,0 +1,73 @@
/*
* 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.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ValidatePassworrdEmailResetPage extends AbstractPage {
@FindBy(id = "key")
private WebElement keyInput;
@FindBy(id="kc-submit")
private WebElement submitButton;
@FindBy(id="kc-cancel")
private WebElement cancelButton;
@FindBy(className = "feedback-success")
private WebElement emailSuccessMessage;
@FindBy(className = "feedback-error")
private WebElement emailErrorMessage;
public void submitCode(String code) {
keyInput.sendKeys(code);
submitButton.click();
}
public void cancel() {
cancelButton.click();
}
public boolean isCurrent() {
return driver.getTitle().equals("Forgot Your Password?");
}
public void open() {
throw new UnsupportedOperationException();
}
public String getSuccessMessage() {
return emailSuccessMessage != null ? emailSuccessMessage.getText() : null;
}
public String getErrorMessage() {
return emailErrorMessage != null ? emailErrorMessage.getText() : null;
}
}