Do not allow verifying email from a different account
Closes #14776 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
f476a42d66
commit
8ff9e71eae
11 changed files with 209 additions and 93 deletions
|
@ -95,3 +95,17 @@ In this release, the server will render the update profile page when the user is
|
||||||
first time using the `idp-review-user-profile.ftl` template.
|
first time using the `idp-review-user-profile.ftl` template.
|
||||||
|
|
||||||
For more details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
For more details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
||||||
|
|
||||||
|
= Performing actions on behalf of another user is not longer possible when the user is already authenticated
|
||||||
|
|
||||||
|
In this release, you can no longer perform actions such as email verification if the user is already authenticated
|
||||||
|
and the action is bound to another user. For instance, a user can not complete the verification email flow if the email link
|
||||||
|
is bound to a different account.
|
||||||
|
|
||||||
|
= Changes to the email verification flow
|
||||||
|
|
||||||
|
In this release, if a user tries to follow the link to verify the email and the email was previously verified, a proper message
|
||||||
|
will be shown.
|
||||||
|
|
||||||
|
In addition to that, a new error (`EMAIL_ALREADY_VERIFIED`) event will be fired to indicate an attempt to verify an already verified email. You can
|
||||||
|
use this event to track possible attempts to hijack user accounts in case the link has leaked or to alert users if they do not recognize the action.
|
|
@ -45,6 +45,7 @@ public interface Errors {
|
||||||
String USERNAME_MISSING = "username_missing";
|
String USERNAME_MISSING = "username_missing";
|
||||||
String USERNAME_IN_USE = "username_in_use";
|
String USERNAME_IN_USE = "username_in_use";
|
||||||
String EMAIL_IN_USE = "email_in_use";
|
String EMAIL_IN_USE = "email_in_use";
|
||||||
|
String EMAIL_ALREADY_VERIFIED = "email_already_verified";
|
||||||
|
|
||||||
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
||||||
String INVALID_CODE = "invalid_code";
|
String INVALID_CODE = "invalid_code";
|
||||||
|
|
|
@ -28,13 +28,17 @@ import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.UriBuilder;
|
import jakarta.ws.rs.core.UriBuilder;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
@ -73,10 +77,20 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
|
||||||
event.event(EventType.IDENTITY_PROVIDER_LINK_ACCOUNT)
|
event.event(EventType.IDENTITY_PROVIDER_LINK_ACCOUNT)
|
||||||
.detail(Details.EMAIL, user.getEmail())
|
.detail(Details.EMAIL, user.getEmail())
|
||||||
.detail(Details.IDENTITY_PROVIDER, token.getIdentityProviderAlias())
|
.detail(Details.IDENTITY_PROVIDER, token.getIdentityProviderAlias())
|
||||||
.detail(Details.IDENTITY_PROVIDER_USERNAME, token.getIdentityProviderUsername())
|
.detail(Details.IDENTITY_PROVIDER_USERNAME, token.getIdentityProviderUsername());
|
||||||
.success();
|
|
||||||
|
|
||||||
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
|
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
|
||||||
|
|
||||||
|
if (user.isEmailVerified() && !isVerifyEmailActionSet(user, authSession)) {
|
||||||
|
event.user(user).error(Errors.EMAIL_ALREADY_VERIFIED);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setAuthenticationSession(session.getContext().getAuthenticationSession())
|
||||||
|
.setInfo(Messages.EMAIL_VERIFIED_ALREADY, user.getEmail())
|
||||||
|
.createInfoPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.success();
|
||||||
|
|
||||||
if (tokenContext.isAuthenticationSessionFresh()) {
|
if (tokenContext.isAuthenticationSessionFresh()) {
|
||||||
token.setOriginalCompoundAuthenticationSessionId(token.getCompoundAuthenticationSessionId());
|
token.setOriginalCompoundAuthenticationSessionId(token.getCompoundAuthenticationSessionId());
|
||||||
|
|
||||||
|
@ -126,4 +140,8 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
|
||||||
return tokenContext.brokerFlow(null, null, authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH));
|
return tokenContext.brokerFlow(null, null, authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isVerifyEmailActionSet(UserModel user, AuthenticationSessionModel authSession) {
|
||||||
|
return Stream.concat(user.getRequiredActionsStream(), authSession.getRequiredActions().stream())
|
||||||
|
.anyMatch(RequiredAction.VERIFY_EMAIL.name()::equals);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.UriBuilder;
|
import jakarta.ws.rs.core.UriBuilder;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
@ -66,14 +68,22 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHandler<Ve
|
||||||
@Override
|
@Override
|
||||||
public Response handleToken(VerifyEmailActionToken token, ActionTokenContext<VerifyEmailActionToken> tokenContext) {
|
public Response handleToken(VerifyEmailActionToken token, ActionTokenContext<VerifyEmailActionToken> tokenContext) {
|
||||||
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
|
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
|
||||||
|
KeycloakSession session = tokenContext.getSession();
|
||||||
|
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
|
||||||
EventBuilder event = tokenContext.getEvent();
|
EventBuilder event = tokenContext.getEvent();
|
||||||
|
|
||||||
event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail());
|
event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail());
|
||||||
|
|
||||||
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
|
if (user.isEmailVerified() && !isVerifyEmailActionSet(user, authSession)) {
|
||||||
|
event.user(user).error(Errors.EMAIL_ALREADY_VERIFIED);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setAuthenticationSession(authSession)
|
||||||
|
.setInfo(Messages.EMAIL_VERIFIED_ALREADY, user.getEmail())
|
||||||
|
.createInfoPage();
|
||||||
|
}
|
||||||
|
|
||||||
final UriInfo uriInfo = tokenContext.getUriInfo();
|
final UriInfo uriInfo = tokenContext.getUriInfo();
|
||||||
final RealmModel realm = tokenContext.getRealm();
|
final RealmModel realm = tokenContext.getRealm();
|
||||||
final KeycloakSession session = tokenContext.getSession();
|
|
||||||
|
|
||||||
if (tokenContext.isAuthenticationSessionFresh()) {
|
if (tokenContext.isAuthenticationSessionFresh()) {
|
||||||
// Update the authentication session in the token
|
// Update the authentication session in the token
|
||||||
|
@ -100,10 +110,10 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHandler<Ve
|
||||||
event.success();
|
event.success();
|
||||||
|
|
||||||
if (token.getCompoundOriginalAuthenticationSessionId() != null) {
|
if (token.getCompoundOriginalAuthenticationSessionId() != null) {
|
||||||
AuthenticationSessionManager asm = new AuthenticationSessionManager(tokenContext.getSession());
|
AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
|
||||||
asm.removeAuthenticationSession(tokenContext.getRealm(), authSession, true);
|
asm.removeAuthenticationSession(tokenContext.getRealm(), authSession, true);
|
||||||
|
|
||||||
return tokenContext.getSession().getProvider(LoginFormsProvider.class)
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
.setAuthenticationSession(authSession)
|
.setAuthenticationSession(authSession)
|
||||||
.setSuccess(Messages.EMAIL_VERIFIED)
|
.setSuccess(Messages.EMAIL_VERIFIED)
|
||||||
.createInfoPage();
|
.createInfoPage();
|
||||||
|
@ -115,4 +125,8 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHandler<Ve
|
||||||
return AuthenticationManager.redirectToRequiredActions(session, realm, authSession, uriInfo, nextAction);
|
return AuthenticationManager.redirectToRequiredActions(session, realm, authSession, uriInfo, nextAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isVerifyEmailActionSet(UserModel user, AuthenticationSessionModel authSession) {
|
||||||
|
return Stream.concat(user.getRequiredActionsStream(), authSession.getRequiredActions().stream())
|
||||||
|
.anyMatch(RequiredAction.VERIFY_EMAIL.name()::equals);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ public class Messages {
|
||||||
public static final String LINK_IDP = "linkIdpMessage";
|
public static final String LINK_IDP = "linkIdpMessage";
|
||||||
|
|
||||||
public static final String EMAIL_VERIFIED = "emailVerifiedMessage";
|
public static final String EMAIL_VERIFIED = "emailVerifiedMessage";
|
||||||
|
public static final String EMAIL_VERIFIED_ALREADY = "emailVerifiedAlreadyMessage";
|
||||||
|
|
||||||
public static final String EMAIL_SENT = "emailSentMessage";
|
public static final String EMAIL_SENT = "emailSentMessage";
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.exceptions.TokenNotActiveException;
|
import org.keycloak.exceptions.TokenNotActiveException;
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.SingleUseObjectKeyModel;
|
import org.keycloak.models.SingleUseObjectKeyModel;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -524,8 +525,10 @@ public class LoginActionsService {
|
||||||
client = realm.getClientByClientId(clientId);
|
client = realm.getClientByClientId(clientId);
|
||||||
}
|
}
|
||||||
AuthenticationSessionManager authenticationSessionManager = new AuthenticationSessionManager(session);
|
AuthenticationSessionManager authenticationSessionManager = new AuthenticationSessionManager(session);
|
||||||
|
KeycloakContext sessionContext = session.getContext();
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
session.getContext().setClient(client);
|
sessionContext.setClient(client);
|
||||||
authSession = authenticationSessionManager.getCurrentAuthenticationSession(realm, client, tabId);
|
authSession = authenticationSessionManager.getCurrentAuthenticationSession(realm, client, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,7 +563,7 @@ public class LoginActionsService {
|
||||||
.withChecks(
|
.withChecks(
|
||||||
// Token introspection checks
|
// Token introspection checks
|
||||||
TokenVerifier.IS_ACTIVE,
|
TokenVerifier.IS_ACTIVE,
|
||||||
new TokenVerifier.RealmUrlCheck(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())),
|
new TokenVerifier.RealmUrlCheck(Urls.realmIssuer(sessionContext.getUri().getBaseUri(), realm.getName())),
|
||||||
ACTION_TOKEN_BASIC_CHECKS
|
ACTION_TOKEN_BASIC_CHECKS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -596,22 +599,15 @@ public class LoginActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now proceed with the verification and handle the token
|
// Now proceed with the verification and handle the token
|
||||||
tokenContext = new ActionTokenContext(session, realm, session.getContext().getUri(), clientConnection, request, event, handler, execution, this::processFlow, this::brokerLoginFlow);
|
tokenContext = new ActionTokenContext(session, realm, sessionContext.getUri(), clientConnection, request, event, handler, execution, this::processFlow, this::brokerLoginFlow);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String tokenAuthSessionCompoundId = handler.getAuthenticationSessionIdFromToken(token, tokenContext, authSession);
|
String tokenAuthSessionCompoundId = handler.getAuthenticationSessionIdFromToken(token, tokenContext, authSession);
|
||||||
|
|
||||||
if (tokenAuthSessionCompoundId != null) {
|
|
||||||
// This can happen if the token contains ID but user opens the link in a new browser
|
|
||||||
String sessionId = AuthenticationSessionCompoundId.encoded(tokenAuthSessionCompoundId).getRootSessionId();
|
|
||||||
LoginActionsServiceChecks.checkNotLoggedInYet(tokenContext, authSession, sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authSession == null) {
|
if (authSession == null) {
|
||||||
authSession = handler.startFreshAuthenticationSession(token, tokenContext);
|
authSession = handler.startFreshAuthenticationSession(token, tokenContext);
|
||||||
tokenContext.setAuthenticationSession(authSession, true);
|
tokenContext.setAuthenticationSession(authSession, true);
|
||||||
} else if (tokenAuthSessionCompoundId == null ||
|
} else if (!LoginActionsServiceChecks.doesAuthenticationSessionFromCookieMatchOneFromToken(tokenContext, authSession, tokenAuthSessionCompoundId)) {
|
||||||
! LoginActionsServiceChecks.doesAuthenticationSessionFromCookieMatchOneFromToken(tokenContext, authSession, tokenAuthSessionCompoundId)) {
|
|
||||||
// There exists an authentication session but no auth session ID was received in the action token
|
// There exists an authentication session but no auth session ID was received in the action token
|
||||||
logger.debugf("Authentication session in progress but no authentication session ID was found in action token %s, restarting.", token.getId());
|
logger.debugf("Authentication session in progress but no authentication session ID was found in action token %s, restarting.", token.getId());
|
||||||
authenticationSessionManager.removeAuthenticationSession(realm, authSession, false);
|
authenticationSessionManager.removeAuthenticationSession(realm, authSession, false);
|
||||||
|
@ -622,13 +618,14 @@ public class LoginActionsService {
|
||||||
processLocaleParam(authSession);
|
processLocaleParam(authSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionContext.setAuthenticationSession(authSession);
|
||||||
initLoginEvent(authSession);
|
initLoginEvent(authSession);
|
||||||
event.event(handler.eventType());
|
event.event(handler.eventType());
|
||||||
|
|
||||||
LoginActionsServiceChecks.checkIsUserValid(token, tokenContext);
|
LoginActionsServiceChecks.checkIsUserValid(token, tokenContext, event);
|
||||||
LoginActionsServiceChecks.checkIsClientValid(token, tokenContext);
|
LoginActionsServiceChecks.checkIsClientValid(token, tokenContext);
|
||||||
|
|
||||||
session.getContext().setClient(authSession.getClient());
|
sessionContext.setClient(authSession.getClient());
|
||||||
|
|
||||||
TokenVerifier.createWithoutSignature(token)
|
TokenVerifier.createWithoutSignature(token)
|
||||||
.withChecks(handler.getVerifiers(tokenContext))
|
.withChecks(handler.getVerifiers(tokenContext))
|
||||||
|
|
|
@ -16,17 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.keycloak.TokenVerifier.Predicate;
|
import org.keycloak.TokenVerifier.Predicate;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.ExplainedVerificationException;
|
import org.keycloak.authentication.ExplainedVerificationException;
|
||||||
import org.keycloak.authentication.actiontoken.ActionTokenContext;
|
import org.keycloak.authentication.actiontoken.ActionTokenContext;
|
||||||
import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationException;
|
import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationException;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.forms.login.LoginFormsProvider;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.SingleUseObjectKeyModel;
|
import org.keycloak.models.SingleUseObjectKeyModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.SingleUseObjectProvider;
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
|
@ -34,7 +35,9 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
import org.keycloak.services.ErrorPageException;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
@ -112,38 +115,11 @@ public class LoginActionsServiceChecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies that the authentication session has not yet been converted to user session, in other words
|
|
||||||
* that the user has not yet completed authentication and logged in.
|
|
||||||
*/
|
|
||||||
public static <T extends JsonWebToken> void checkNotLoggedInYet(ActionTokenContext<T> context, AuthenticationSessionModel authSessionFromCookie, String authSessionId) throws VerificationException {
|
|
||||||
if (authSessionId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSessionModel userSession = context.getSession().sessions().getUserSession(context.getRealm(), authSessionId);
|
|
||||||
boolean hasNoRequiredActions =
|
|
||||||
(userSession == null || userSession.getUser().getRequiredActionsStream().count() == 0)
|
|
||||||
&&
|
|
||||||
(authSessionFromCookie == null || authSessionFromCookie.getRequiredActions() == null || authSessionFromCookie.getRequiredActions().isEmpty());
|
|
||||||
|
|
||||||
if (userSession != null && hasNoRequiredActions) {
|
|
||||||
LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class).setAuthenticationSession(context.getAuthenticationSession())
|
|
||||||
.setSuccess(Messages.ALREADY_LOGGED_IN);
|
|
||||||
|
|
||||||
if (context.getSession().getContext().getClient() == null) {
|
|
||||||
loginForm.setAttribute(Constants.SKIP_LINK, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new LoginActionsServiceException(loginForm.createInfoPage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies whether the user given by ID both exists in the current realm. If yes,
|
* Verifies whether the user given by ID both exists in the current realm. If yes,
|
||||||
* it optionally also injects the user using the given function (e.g. into session context).
|
* it optionally also injects the user using the given function (e.g. into session context).
|
||||||
*/
|
*/
|
||||||
public static void checkIsUserValid(KeycloakSession session, RealmModel realm, String userId, Consumer<UserModel> userSetter) throws VerificationException {
|
public static void checkIsUserValid(KeycloakSession session, RealmModel realm, String userId, Consumer<UserModel> userSetter, EventBuilder event) throws VerificationException {
|
||||||
UserModel user = userId == null ? null : session.users().getUserById(realm, userId);
|
UserModel user = userId == null ? null : session.users().getUserById(realm, userId);
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -154,6 +130,21 @@ public class LoginActionsServiceChecks {
|
||||||
throw new ExplainedVerificationException(Errors.USER_DISABLED, Messages.ACCOUNT_DISABLED);
|
throw new ExplainedVerificationException(Errors.USER_DISABLED, Messages.ACCOUNT_DISABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, true);
|
||||||
|
|
||||||
|
if (authResult != null) {
|
||||||
|
UserSessionModel userSession = authResult.getSession();
|
||||||
|
if (!user.equals(userSession.getUser())) {
|
||||||
|
// do not allow authenticated users performing actions that are bound to other user and fire an event
|
||||||
|
// it might be an attempt to hijack a user account or perform actions on behalf of others
|
||||||
|
// we don't support yet multiple accounts within a same browser session
|
||||||
|
event.detail(Details.EXISTING_USER, userSession.getUser().getId());
|
||||||
|
event.error(Errors.DIFFERENT_USER_AUTHENTICATED);
|
||||||
|
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||||
|
throw new ErrorPageException(session, authSession, Response.Status.BAD_REQUEST, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (userSetter != null) {
|
if (userSetter != null) {
|
||||||
userSetter.accept(user);
|
userSetter.accept(user);
|
||||||
}
|
}
|
||||||
|
@ -163,9 +154,9 @@ public class LoginActionsServiceChecks {
|
||||||
* Verifies whether the user given by ID both exists in the current realm. If yes,
|
* Verifies whether the user given by ID both exists in the current realm. If yes,
|
||||||
* it optionally also injects the user using the given function (e.g. into session context).
|
* it optionally also injects the user using the given function (e.g. into session context).
|
||||||
*/
|
*/
|
||||||
public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkIsUserValid(T token, ActionTokenContext<T> context) throws VerificationException {
|
public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkIsUserValid(T token, ActionTokenContext<T> context, EventBuilder event) throws VerificationException {
|
||||||
try {
|
try {
|
||||||
checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser);
|
checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser, event);
|
||||||
} catch (ExplainedVerificationException ex) {
|
} catch (ExplainedVerificationException ex) {
|
||||||
throw new ExplainedTokenVerificationException(token, ex);
|
throw new ExplainedTokenVerificationException(token, ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
@ -81,6 +82,8 @@ import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
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>
|
||||||
|
@ -171,7 +174,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
|
AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
|
||||||
EventRepresentation sendEvent = emailEvent.assertEvent();
|
EventRepresentation sendEvent = emailEvent.assertEvent();
|
||||||
|
@ -209,7 +212,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
EventRepresentation sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail(Details.USERNAME, "verifyemail").detail("email", "email@mail.com").assertEvent();
|
EventRepresentation sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail(Details.USERNAME, "verifyemail").detail("email", "email@mail.com").assertEvent();
|
||||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl.trim());
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
@ -225,6 +228,60 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
events.expectLogin().user(userId).session(mailCodeId).detail(Details.USERNAME, "verifyemail").assertEvent();
|
events.expectLogin().user(userId).session(mailCodeId).detail(Details.USERNAME, "verifyemail").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyEmailFromAnotherAccountWhenUserIsAuthenticated() throws Exception {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.clickRegister();
|
||||||
|
String username1 = KeycloakModelUtils.generateId();
|
||||||
|
registerPage.register("firstName", "lastName", username1 + "@mail.com", username1, "password", "password");
|
||||||
|
verifyEmailPage.assertCurrent();
|
||||||
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
String verificationLink1 = getEmailLink(message);
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.clickRegister();
|
||||||
|
String username2 = KeycloakModelUtils.generateId();
|
||||||
|
registerPage.register("firstName", "lastName", username2 + "@mail.com", username2, "password", "password");
|
||||||
|
verifyEmailPage.assertCurrent();
|
||||||
|
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
||||||
|
message = greenMail.getReceivedMessages()[1];
|
||||||
|
String verificationLink2 = getEmailLink(message);
|
||||||
|
driver.navigate().to(verificationLink2.trim());
|
||||||
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
driver.navigate().to(verificationLink1.trim());
|
||||||
|
assertTrue(errorPage.getError().contains("You are already authenticated as different user"));
|
||||||
|
UserRepresentation user1 = testRealm().users().search(username1).get(0);
|
||||||
|
UserRepresentation user2 = testRealm().users().search(username2).get(0);
|
||||||
|
assertFalse(user1.isEmailVerified());
|
||||||
|
assertTrue(user2.isEmailVerified());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyEmailFromAnotherAccountAfterEmalIsVerified() throws Exception {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.clickRegister();
|
||||||
|
String username1 = KeycloakModelUtils.generateId();
|
||||||
|
registerPage.register("firstName", "lastName", username1 + "@mail.com", username1, "password", "password");
|
||||||
|
verifyEmailPage.assertCurrent();
|
||||||
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
String verificationLink1 = getEmailLink(message);
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.clickRegister();
|
||||||
|
String username2 = KeycloakModelUtils.generateId();
|
||||||
|
registerPage.register("firstName", "lastName", username2 + "@mail.com", username2, "password", "password");
|
||||||
|
verifyEmailPage.assertCurrent();
|
||||||
|
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
||||||
|
message = greenMail.getReceivedMessages()[1];
|
||||||
|
String verificationLink2 = getEmailLink(message);
|
||||||
|
|
||||||
|
driver.navigate().to(verificationLink1.trim());
|
||||||
|
driver.navigate().to(verificationLink2.trim());
|
||||||
|
assertTrue(errorPage.getError().contains("You are already authenticated as different user"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void verifyEmailResend() throws IOException, MessagingException {
|
public void verifyEmailResend() throws IOException, MessagingException {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
@ -250,7 +307,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl.trim());
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
@ -294,7 +351,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl.trim());
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
@ -324,7 +381,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message1 = greenMail.getReceivedMessages()[0];
|
MimeMessage message1 = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
String verificationUrl1 = getPasswordResetEmailLink(message1);
|
String verificationUrl1 = getEmailLink(message1);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl1.trim());
|
driver.navigate().to(verificationUrl1.trim());
|
||||||
|
|
||||||
|
@ -333,12 +390,16 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message2 = greenMail.getReceivedMessages()[1];
|
MimeMessage message2 = greenMail.getReceivedMessages()[1];
|
||||||
|
|
||||||
String verificationUrl2 = getPasswordResetEmailLink(message2);
|
String verificationUrl2 = getEmailLink(message2);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
driver.navigate().to(verificationUrl2.trim());
|
driver.navigate().to(verificationUrl2.trim());
|
||||||
|
events.expectRequiredAction(EventType.VERIFY_EMAIL)
|
||||||
|
.error(Errors.EMAIL_ALREADY_VERIFIED)
|
||||||
|
.detail(Details.REDIRECT_URI, Matchers.any(String.class))
|
||||||
|
.assertEvent();
|
||||||
infoPage.assertCurrent();
|
infoPage.assertCurrent();
|
||||||
Assert.assertEquals("You are already logged in.", infoPage.getInfo());
|
Assert.assertEquals("Your email address has been verified already.", infoPage.getInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -354,7 +415,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message1 = greenMail.getReceivedMessages()[0];
|
MimeMessage message1 = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
String verificationUrl1 = getPasswordResetEmailLink(message1);
|
String verificationUrl1 = getEmailLink(message1);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl1.trim());
|
driver.navigate().to(verificationUrl1.trim());
|
||||||
|
|
||||||
|
@ -362,14 +423,31 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message2 = greenMail.getReceivedMessages()[1];
|
MimeMessage message2 = greenMail.getReceivedMessages()[1];
|
||||||
|
|
||||||
String verificationUrl2 = getPasswordResetEmailLink(message2);
|
String verificationUrl2 = getEmailLink(message2);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl2.trim());
|
driver.navigate().to(verificationUrl2.trim());
|
||||||
|
|
||||||
proceedPage.assertCurrent();
|
assertEquals("Your email address has been verified already.", infoPage.getInfo());
|
||||||
proceedPage.clickProceedLink();
|
}
|
||||||
infoPage.assertCurrent();
|
|
||||||
assertEquals("Your email address has been verified.", infoPage.getInfo());
|
@Test
|
||||||
|
public void verifyEmailResendAndVerifyWithLatestLink() throws IOException, MessagingException {
|
||||||
|
// Email verification can be performed any number of times
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
verifyEmailPage.clickResendEmail();
|
||||||
|
verifyEmailPage.assertCurrent();
|
||||||
|
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
||||||
|
MimeMessage message1 = greenMail.getReceivedMessages()[0];
|
||||||
|
String verificationUrl1 = getEmailLink(message1);
|
||||||
|
|
||||||
|
MimeMessage message2 = greenMail.getReceivedMessages()[1];
|
||||||
|
String verificationUrl2 = getEmailLink(message2);
|
||||||
|
driver.navigate().to(verificationUrl2.trim());
|
||||||
|
appPage.assertCurrent();
|
||||||
|
|
||||||
|
driver.navigate().to(verificationUrl1.trim());
|
||||||
|
assertEquals("Your email address has been verified already.", infoPage.getInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -383,7 +461,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
|
AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
|
||||||
EventRepresentation sendEvent = emailEvent.assertEvent();
|
EventRepresentation sendEvent = emailEvent.assertEvent();
|
||||||
|
@ -422,7 +500,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
verificationUrl = KeycloakUriBuilder.fromUri(verificationUrl).replaceQueryParam(Constants.KEY, "foo").build().toString();
|
verificationUrl = KeycloakUriBuilder.fromUri(verificationUrl).replaceQueryParam(Constants.KEY, "foo").build().toString();
|
||||||
|
|
||||||
|
@ -453,7 +531,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
events.poll();
|
events.poll();
|
||||||
|
|
||||||
|
@ -495,7 +573,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
events.poll();
|
events.poll();
|
||||||
|
|
||||||
|
@ -540,7 +618,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
events.poll();
|
events.poll();
|
||||||
|
|
||||||
|
@ -578,7 +656,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
events.poll();
|
events.poll();
|
||||||
|
|
||||||
|
@ -606,7 +684,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
|
public static String getEmailLink(MimeMessage message) throws IOException, MessagingException {
|
||||||
return MailUtils.getPasswordResetEmailLink(message);
|
return MailUtils.getPasswordResetEmailLink(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,7 +699,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
|
|
||||||
|
@ -650,7 +728,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
// open link in the second browser without the session
|
// open link in the second browser without the session
|
||||||
driver2.navigate().to(verificationUrl.trim());
|
driver2.navigate().to(verificationUrl.trim());
|
||||||
|
@ -688,7 +766,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl.trim());
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
@ -707,7 +785,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
|
|
||||||
|
@ -732,7 +810,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
|
|
||||||
|
@ -808,7 +886,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver2.navigate().to(verificationUrl.trim());
|
driver2.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
@ -846,7 +924,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
// confirm
|
// confirm
|
||||||
driver.navigate().to(verificationUrl);
|
driver.navigate().to(verificationUrl);
|
||||||
|
@ -856,7 +934,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
// email should be verified and required actions empty
|
// email should be verified and required actions empty
|
||||||
UserRepresentation user = testRealm().users().get(testUserId).toRepresentation();
|
UserRepresentation user = testRealm().users().get(testUserId).toRepresentation();
|
||||||
Assert.assertTrue(user.isEmailVerified());
|
assertTrue(user.isEmailVerified());
|
||||||
assertThat(user.getRequiredActions(), Matchers.empty());
|
assertThat(user.getRequiredActions(), Matchers.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,7 +963,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
// confirm
|
// confirm
|
||||||
driver.navigate().to(verificationUrl);
|
driver.navigate().to(verificationUrl);
|
||||||
|
@ -895,7 +973,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
// email should be verified and required actions empty
|
// email should be verified and required actions empty
|
||||||
UserRepresentation user = testRealm().users().get(testUserId).toRepresentation();
|
UserRepresentation user = testRealm().users().get(testUserId).toRepresentation();
|
||||||
Assert.assertTrue(user.isEmailVerified());
|
assertTrue(user.isEmailVerified());
|
||||||
assertThat(user.getRequiredActions(), Matchers.empty());
|
assertThat(user.getRequiredActions(), Matchers.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,7 +995,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
// confirm in the second browser
|
// confirm in the second browser
|
||||||
driver2.navigate().to(verificationUrl);
|
driver2.navigate().to(verificationUrl);
|
||||||
|
@ -939,7 +1017,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
// email should be verified and required actions empty
|
// email should be verified and required actions empty
|
||||||
UserRepresentation user = testRealm().users().get(testUserId).toRepresentation();
|
UserRepresentation user = testRealm().users().get(testUserId).toRepresentation();
|
||||||
Assert.assertTrue(user.isEmailVerified());
|
assertTrue(user.isEmailVerified());
|
||||||
assertThat(user.getRequiredActions(), Matchers.empty());
|
assertThat(user.getRequiredActions(), Matchers.empty());
|
||||||
|
|
||||||
// after refresh in the first browser the app should be shown
|
// after refresh in the first browser the app should be shown
|
||||||
|
@ -964,7 +1042,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
|
|
||||||
MimeMessage message = greenMail.getLastReceivedMessage();
|
MimeMessage message = greenMail.getLastReceivedMessage();
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setTimeOffset(360);
|
setTimeOffset(360);
|
||||||
|
@ -989,7 +1067,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
assertEquals(1, greenMail.getReceivedMessages().length);
|
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
UserResource user = testRealm().users().get(testUserId);
|
UserResource user = testRealm().users().get(testUserId);
|
||||||
UserRepresentation userRep = user.toRepresentation();
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
|
|
|
@ -909,13 +909,14 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
||||||
assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(consumerUser.getId()).toRepresentation().isEmailVerified());
|
assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(consumerUser.getId()).toRepresentation().isEmailVerified());
|
||||||
|
|
||||||
driver.navigate().to(url);
|
driver.navigate().to(url);
|
||||||
waitForPage(driver, "you are already logged in.", false);
|
waitForPage(driver, "your email address has been verified already.", false);
|
||||||
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), "consumer");
|
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), "consumer");
|
||||||
|
|
||||||
driver.navigate().to(url);
|
driver.navigate().to(url);
|
||||||
waitForPage(driver, "confirm linking the account testuser of identity provider " + bc.getIDPAlias() + " with your account.", false);
|
waitForPage(driver, "your email address has been verified already.", false);
|
||||||
proceedPage.clickProceedLink();
|
|
||||||
waitForPage(driver, "you successfully verified your email. please go back to your original browser and continue there with the login.", false);
|
driver2.navigate().to(url);
|
||||||
|
waitForPage(driver, "your email address has been verified already.", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ import static org.keycloak.storage.UserStorageProviderModel.EVICTION_HOUR;
|
||||||
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_MINUTE;
|
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_MINUTE;
|
||||||
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
|
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
|
||||||
import static org.keycloak.storage.UserStorageProviderModel.MAX_LIFESPAN;
|
import static org.keycloak.storage.UserStorageProviderModel.MAX_LIFESPAN;
|
||||||
import static org.keycloak.testsuite.actions.RequiredActionEmailVerificationTest.getPasswordResetEmailLink;
|
import static org.keycloak.testsuite.actions.RequiredActionEmailVerificationTest.getEmailLink;
|
||||||
|
|
||||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
|
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ public class UserStorageTest extends AbstractAuthTest {
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||||
|
|
||||||
String verificationUrl = getPasswordResetEmailLink(message);
|
String verificationUrl = getEmailLink(message);
|
||||||
|
|
||||||
driver.navigate().to(verificationUrl.trim());
|
driver.navigate().to(verificationUrl.trim());
|
||||||
|
|
||||||
|
|
|
@ -353,6 +353,7 @@ realmSupportsNoCredentialsMessage=Realm does not support any credential type.
|
||||||
credentialSetupRequired=Cannot login, credential setup required.
|
credentialSetupRequired=Cannot login, credential setup required.
|
||||||
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
|
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
|
||||||
emailVerifiedMessage=Your email address has been verified.
|
emailVerifiedMessage=Your email address has been verified.
|
||||||
|
emailVerifiedAlreadyMessage=Your email address has been verified already.
|
||||||
staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email.
|
staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email.
|
||||||
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
|
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
|
||||||
confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.
|
confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.
|
||||||
|
|
Loading…
Reference in a new issue