KEYCLOAK-15170 Reset password link is not invalidated if email address is changed
This commit is contained in:
parent
4f08912071
commit
57fca2a34f
15 changed files with 107 additions and 37 deletions
|
@ -17,18 +17,21 @@
|
||||||
package org.keycloak.authentication.actiontoken;
|
package org.keycloak.authentication.actiontoken;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.TokenVerifier;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractActionTokenHander<T extends JsonWebToken> implements ActionTokenHandler<T>, ActionTokenHandlerFactory<T> {
|
public abstract class AbstractActionTokenHandler<T extends JsonWebToken> implements ActionTokenHandler<T>, ActionTokenHandlerFactory<T> {
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final Class<T> tokenClass;
|
private final Class<T> tokenClass;
|
||||||
|
@ -36,7 +39,7 @@ public abstract class AbstractActionTokenHander<T extends JsonWebToken> implemen
|
||||||
private final EventType defaultEventType;
|
private final EventType defaultEventType;
|
||||||
private final String defaultEventError;
|
private final String defaultEventError;
|
||||||
|
|
||||||
public AbstractActionTokenHander(String id, Class<T> tokenClass, String defaultErrorMessage, EventType defaultEventType, String defaultEventError) {
|
public AbstractActionTokenHandler(String id, Class<T> tokenClass, String defaultErrorMessage, EventType defaultEventType, String defaultEventError) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.tokenClass = tokenClass;
|
this.tokenClass = tokenClass;
|
||||||
this.defaultErrorMessage = defaultErrorMessage;
|
this.defaultErrorMessage = defaultErrorMessage;
|
||||||
|
@ -102,4 +105,11 @@ public abstract class AbstractActionTokenHander<T extends JsonWebToken> implemen
|
||||||
public boolean canUseTokenRepeatedly(T token, ActionTokenContext<T> tokenContext) {
|
public boolean canUseTokenRepeatedly(T token, ActionTokenContext<T> tokenContext) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TokenVerifier.Predicate<DefaultActionToken> verifyEmail(ActionTokenContext<? extends DefaultActionToken> context) {
|
||||||
|
return TokenUtils.checkThat(
|
||||||
|
t -> t.getEmail() == null || t.getEmail().equals(context.getAuthenticationSession().getAuthenticatedUser().getEmail()),
|
||||||
|
Errors.INVALID_EMAIL, Messages.INVALID_EMAIL
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -17,16 +17,20 @@
|
||||||
|
|
||||||
package org.keycloak.authentication.actiontoken;
|
package org.keycloak.authentication.actiontoken;
|
||||||
|
|
||||||
import org.keycloak.TokenVerifier.Predicate;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.models.*;
|
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import java.util.*;
|
import org.keycloak.TokenVerifier.Predicate;
|
||||||
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.models.ActionTokenValueModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.services.Urls;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of action token that is intended to be used e.g. in link sent in password-reset email.
|
* Part of action token that is intended to be used e.g. in link sent in password-reset email.
|
||||||
|
@ -37,6 +41,10 @@ import javax.ws.rs.core.UriInfo;
|
||||||
public class DefaultActionToken extends DefaultActionTokenKey implements ActionTokenValueModel {
|
public class DefaultActionToken extends DefaultActionTokenKey implements ActionTokenValueModel {
|
||||||
|
|
||||||
public static final String JSON_FIELD_AUTHENTICATION_SESSION_ID = "asid";
|
public static final String JSON_FIELD_AUTHENTICATION_SESSION_ID = "asid";
|
||||||
|
public static final String JSON_FIELD_EMAIL = "eml";
|
||||||
|
|
||||||
|
@JsonProperty(value = JSON_FIELD_EMAIL)
|
||||||
|
private String email;
|
||||||
|
|
||||||
public static final Predicate<DefaultActionTokenKey> ACTION_TOKEN_BASIC_CHECKS = t -> {
|
public static final Predicate<DefaultActionTokenKey> ACTION_TOKEN_BASIC_CHECKS = t -> {
|
||||||
if (t.getActionVerificationNonce() == null) {
|
if (t.getActionVerificationNonce() == null) {
|
||||||
|
@ -122,6 +130,14 @@ public class DefaultActionToken extends DefaultActionTokenKey implements ActionT
|
||||||
return res instanceof String ? (String) res : null;
|
return res instanceof String ? (String) res : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the following fields and serializes this token into a signed JWT. The list of updated fields follows:
|
* Updates the following fields and serializes this token into a signed JWT. The list of updated fields follows:
|
||||||
* <ul>
|
* <ul>
|
||||||
|
|
|
@ -38,6 +38,11 @@ public class ExecuteActionsActionToken extends DefaultActionToken {
|
||||||
this.issuedFor = clientId;
|
this.issuedFor = clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ExecuteActionsActionToken(String userId, String email, int absoluteExpirationInSecs, List<String> requiredActions, String redirectUri, String clientId) {
|
||||||
|
this(userId, absoluteExpirationInSecs, requiredActions, redirectUri, clientId);
|
||||||
|
setEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
private ExecuteActionsActionToken() {
|
private ExecuteActionsActionToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<ExecuteActionsActionToken> {
|
public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHandler<ExecuteActionsActionToken> {
|
||||||
|
|
||||||
public ExecuteActionsActionTokenHandler() {
|
public ExecuteActionsActionTokenHandler() {
|
||||||
super(
|
super(
|
||||||
|
@ -62,7 +62,9 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
|
||||||
tokenContext.getAuthenticationSession().getClient()) != null,
|
tokenContext.getAuthenticationSession().getClient()) != null,
|
||||||
Errors.INVALID_REDIRECT_URI,
|
Errors.INVALID_REDIRECT_URI,
|
||||||
Messages.INVALID_REDIRECT_URI
|
Messages.INVALID_REDIRECT_URI
|
||||||
)
|
),
|
||||||
|
|
||||||
|
verifyEmail(tokenContext)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,13 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
|
||||||
private String originalAuthenticationSessionId;
|
private String originalAuthenticationSessionId;
|
||||||
|
|
||||||
|
|
||||||
public IdpVerifyAccountLinkActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId,
|
public IdpVerifyAccountLinkActionToken(String userId, String email, int absoluteExpirationInSecs, String compoundAuthenticationSessionId,
|
||||||
String identityProviderUsername, String identityProviderAlias, String clientId) {
|
String identityProviderUsername, String identityProviderAlias, String clientId) {
|
||||||
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
|
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
|
||||||
this.identityProviderUsername = identityProviderUsername;
|
this.identityProviderUsername = identityProviderUsername;
|
||||||
this.identityProviderAlias = identityProviderAlias;
|
this.identityProviderAlias = identityProviderAlias;
|
||||||
this.issuedFor = clientId;
|
this.issuedFor = clientId;
|
||||||
|
setEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdpVerifyAccountLinkActionToken() {
|
private IdpVerifyAccountLinkActionToken() {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authentication.actiontoken.idpverifyemail;
|
package org.keycloak.authentication.actiontoken.idpverifyemail;
|
||||||
|
|
||||||
import org.keycloak.authentication.actiontoken.AbstractActionTokenHander;
|
import org.keycloak.authentication.actiontoken.AbstractActionTokenHandler;
|
||||||
import org.keycloak.TokenVerifier.Predicate;
|
import org.keycloak.TokenVerifier.Predicate;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.actiontoken.*;
|
import org.keycloak.authentication.actiontoken.*;
|
||||||
|
@ -43,7 +43,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
* Action token handler for verification of e-mail address.
|
* Action token handler for verification of e-mail address.
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenHander<IdpVerifyAccountLinkActionToken> {
|
public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenHandler<IdpVerifyAccountLinkActionToken> {
|
||||||
|
|
||||||
public IdpVerifyAccountLinkActionTokenHandler() {
|
public IdpVerifyAccountLinkActionTokenHandler() {
|
||||||
super(
|
super(
|
||||||
|
@ -58,6 +58,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
|
||||||
@Override
|
@Override
|
||||||
public Predicate<? super IdpVerifyAccountLinkActionToken>[] getVerifiers(ActionTokenContext<IdpVerifyAccountLinkActionToken> tokenContext) {
|
public Predicate<? super IdpVerifyAccountLinkActionToken>[] getVerifiers(ActionTokenContext<IdpVerifyAccountLinkActionToken> tokenContext) {
|
||||||
return TokenUtils.predicates(
|
return TokenUtils.predicates(
|
||||||
|
verifyEmail(tokenContext)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,10 @@ public class ResetCredentialsActionToken extends DefaultActionToken {
|
||||||
|
|
||||||
public static final String TOKEN_TYPE = "reset-credentials";
|
public static final String TOKEN_TYPE = "reset-credentials";
|
||||||
|
|
||||||
public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String clientId) {
|
public ResetCredentialsActionToken(String userId, String email, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String clientId) {
|
||||||
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
|
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
|
||||||
this.issuedFor = clientId;
|
this.issuedFor = clientId;
|
||||||
|
setEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResetCredentialsActionToken() {
|
private ResetCredentialsActionToken() {
|
||||||
|
|
|
@ -24,19 +24,19 @@ import org.keycloak.authentication.authenticators.broker.util.SerializedBrokered
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.ErrorPage;
|
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.resources.LoginActionsServiceChecks.IsActionRequired;
|
import org.keycloak.services.resources.LoginActionsServiceChecks.IsActionRequired;
|
||||||
import org.keycloak.sessions.CommonClientSessionModel.Action;
|
import org.keycloak.sessions.CommonClientSessionModel.Action;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import static org.keycloak.services.resources.LoginActionsService.RESET_CREDENTIALS_PATH;
|
import static org.keycloak.services.resources.LoginActionsService.RESET_CREDENTIALS_PATH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHander<ResetCredentialsActionToken> {
|
public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHandler<ResetCredentialsActionToken> {
|
||||||
|
|
||||||
public ResetCredentialsActionTokenHandler() {
|
public ResetCredentialsActionTokenHandler() {
|
||||||
super(
|
super(
|
||||||
|
@ -51,11 +51,13 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Predicate<? super ResetCredentialsActionToken>[] getVerifiers(ActionTokenContext<ResetCredentialsActionToken> tokenContext) {
|
public Predicate<? super ResetCredentialsActionToken>[] getVerifiers(ActionTokenContext<ResetCredentialsActionToken> tokenContext) {
|
||||||
return new Predicate[] {
|
return TokenUtils.predicates(
|
||||||
TokenUtils.checkThat(tokenContext.getRealm()::isResetPasswordAllowed, Errors.NOT_ALLOWED, Messages.RESET_CREDENTIAL_NOT_ALLOWED),
|
TokenUtils.checkThat(tokenContext.getRealm()::isResetPasswordAllowed, Errors.NOT_ALLOWED, Messages.RESET_CREDENTIAL_NOT_ALLOWED),
|
||||||
|
|
||||||
|
verifyEmail(tokenContext),
|
||||||
|
|
||||||
new IsActionRequired(tokenContext, Action.AUTHENTICATE)
|
new IsActionRequired(tokenContext, Action.AUTHENTICATE)
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,32 +28,20 @@ public class VerifyEmailActionToken extends DefaultActionToken {
|
||||||
|
|
||||||
public static final String TOKEN_TYPE = "verify-email";
|
public static final String TOKEN_TYPE = "verify-email";
|
||||||
|
|
||||||
private static final String JSON_FIELD_EMAIL = "eml";
|
|
||||||
private static final String JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID = "oasid";
|
private static final String JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID = "oasid";
|
||||||
|
|
||||||
@JsonProperty(value = JSON_FIELD_EMAIL)
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
@JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID)
|
@JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID)
|
||||||
private String originalAuthenticationSessionId;
|
private String originalAuthenticationSessionId;
|
||||||
|
|
||||||
public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String email, String clientId) {
|
public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String email, String clientId) {
|
||||||
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
|
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
|
||||||
this.email = email;
|
setEmail(email);
|
||||||
this.issuedFor = clientId;
|
this.issuedFor = clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VerifyEmailActionToken() {
|
private VerifyEmailActionToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCompoundOriginalAuthenticationSessionId() {
|
public String getCompoundOriginalAuthenticationSessionId() {
|
||||||
return originalAuthenticationSessionId;
|
return originalAuthenticationSessionId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authentication.actiontoken.verifyemail;
|
package org.keycloak.authentication.actiontoken.verifyemail;
|
||||||
|
|
||||||
import org.keycloak.authentication.actiontoken.AbstractActionTokenHander;
|
import org.keycloak.authentication.actiontoken.AbstractActionTokenHandler;
|
||||||
import org.keycloak.TokenVerifier.Predicate;
|
import org.keycloak.TokenVerifier.Predicate;
|
||||||
import org.keycloak.authentication.actiontoken.*;
|
import org.keycloak.authentication.actiontoken.*;
|
||||||
import org.keycloak.events.*;
|
import org.keycloak.events.*;
|
||||||
|
@ -41,7 +41,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
* Action token handler for verification of e-mail address.
|
* Action token handler for verification of e-mail address.
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<VerifyEmailActionToken> {
|
public class VerifyEmailActionTokenHandler extends AbstractActionTokenHandler<VerifyEmailActionToken> {
|
||||||
|
|
||||||
public VerifyEmailActionTokenHandler() {
|
public VerifyEmailActionTokenHandler() {
|
||||||
super(
|
super(
|
||||||
|
|
|
@ -129,7 +129,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
|
||||||
|
|
||||||
String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId();
|
String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId();
|
||||||
IdpVerifyAccountLinkActionToken token = new IdpVerifyAccountLinkActionToken(
|
IdpVerifyAccountLinkActionToken token = new IdpVerifyAccountLinkActionToken(
|
||||||
existingUser.getId(), absoluteExpirationInSecs, authSessionEncodedId,
|
existingUser.getId(), existingUser.getEmail(), absoluteExpirationInSecs, authSessionEncodedId,
|
||||||
brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias(), authSession.getClient().getClientId()
|
brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias(), authSession.getClient().getClientId()
|
||||||
);
|
);
|
||||||
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo),
|
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo),
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
|
||||||
|
|
||||||
// We send the secret in the email in a link as a query param.
|
// We send the secret in the email in a link as a query param.
|
||||||
String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authenticationSession).getEncodedId();
|
String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authenticationSession).getEncodedId();
|
||||||
ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, authenticationSession.getClient().getClientId());
|
ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), user.getEmail(), absoluteExpirationInSecs, authSessionEncodedId, authenticationSession.getClient().getClientId());
|
||||||
String link = UriBuilder
|
String link = UriBuilder
|
||||||
.fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo())))
|
.fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo())))
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -781,7 +781,7 @@ public class UserResource {
|
||||||
lifespan = realm.getActionTokenGeneratedByAdminLifespan();
|
lifespan = realm.getActionTokenGeneratedByAdminLifespan();
|
||||||
}
|
}
|
||||||
int expiration = Time.currentTime() + lifespan;
|
int expiration = Time.currentTime() + lifespan;
|
||||||
ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), expiration, actions, redirectUri, clientId);
|
ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), user.getEmail(), expiration, actions, redirectUri, clientId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
UriBuilder builder = LoginActionsService.actionTokenProcessor(session.getContext().getUri());
|
UriBuilder builder = LoginActionsService.actionTokenProcessor(session.getContext().getUri());
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.keycloak.testsuite.actions;
|
package org.keycloak.testsuite.actions;
|
||||||
|
|
||||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
|
import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -999,4 +1000,27 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
setTimeOffset(0);
|
setTimeOffset(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-15170
|
||||||
|
@Test
|
||||||
|
public void changeEmailAddressAfterSendingEmail() throws Exception {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
verifyEmailPage.assertCurrent();
|
||||||
|
|
||||||
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
String verificationUrl = getPasswordResetEmailLink(message);
|
||||||
|
|
||||||
|
UserResource user = testRealm().users().get(testUserId);
|
||||||
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
|
userRep.setEmail("vmuzikar@redhat.com");
|
||||||
|
user.update(userRep);
|
||||||
|
|
||||||
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
errorPage.assertCurrent();
|
||||||
|
assertEquals("The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email.", errorPage.getError());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1156,6 +1156,26 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-15170
|
||||||
|
@Test
|
||||||
|
public void changeEmailAddressAfterSendingEmail() throws IOException {
|
||||||
|
initiateResetPasswordFromResetPasswordPage(defaultUser.getUsername());
|
||||||
|
|
||||||
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
|
||||||
|
|
||||||
|
UserResource user = testRealm().users().get(defaultUser.getId());
|
||||||
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
|
userRep.setEmail("vmuzikar@redhat.com");
|
||||||
|
user.update(userRep);
|
||||||
|
|
||||||
|
driver.navigate().to(changePasswordUrl.trim());
|
||||||
|
errorPage.assertCurrent();
|
||||||
|
assertEquals("Invalid email address.", errorPage.getError());
|
||||||
|
}
|
||||||
|
|
||||||
private void changePasswordOnUpdatePage(WebDriver driver) {
|
private void changePasswordOnUpdatePage(WebDriver driver) {
|
||||||
assertThat(driver.getPageSource(), Matchers.containsString("You need to change your password."));
|
assertThat(driver.getPageSource(), Matchers.containsString("You need to change your password."));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue