KEYCLOAK-6455 Ability to require email to be verified before changing (#7943)
Closes #11875
This commit is contained in:
parent
76f83f0ab2
commit
5d87cdf1c6
78 changed files with 1976 additions and 277 deletions
|
@ -17,17 +17,16 @@
|
|||
|
||||
package org.keycloak.common;
|
||||
|
||||
import static org.keycloak.common.Profile.Type.DEPRECATED;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import static org.keycloak.common.Profile.Type.DEPRECATED;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -163,7 +162,8 @@ public class Profile {
|
|||
DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL),
|
||||
CLIENT_SECRET_ROTATION("Client Secret Rotation", Type.PREVIEW),
|
||||
STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT),
|
||||
RECOVERY_CODES("Recovery codes", Type.PREVIEW);
|
||||
RECOVERY_CODES("Recovery codes", Type.PREVIEW),
|
||||
UPDATE_EMAIL("Update Email Action", Type.PREVIEW);
|
||||
|
||||
|
||||
private final Type typeProject;
|
||||
|
|
|
@ -24,8 +24,8 @@ public class ProfileTest {
|
|||
@Test
|
||||
public void checkDefaultsKeycloak() {
|
||||
Assert.assertEquals("community", Profile.getName());
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN2, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION);
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN2, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -36,8 +36,8 @@ public class ProfileTest {
|
|||
Profile.init();
|
||||
|
||||
Assert.assertEquals("product", Profile.getName());
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN2, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION);
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN2, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE, Feature.CLIENT_SECRET_ROTATION, Feature.UPDATE_EMAIL);
|
||||
|
||||
System.setProperty("keycloak.profile", "community");
|
||||
Version.NAME = backUpName;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.federation.kerberos;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.credential.CredentialAuthentication;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
|
@ -271,6 +272,9 @@ public class KerberosFederationProvider implements UserStorageProvider,
|
|||
user.setSingleAttribute(KERBEROS_PRINCIPAL, username + "@" + kerberosConfig.getKerberosRealm());
|
||||
|
||||
if (kerberosConfig.isUpdateProfileFirstLogin()) {
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL)) {
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL);
|
||||
}
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,5 +27,9 @@ import java.util.Map;
|
|||
*/
|
||||
public interface EmailSenderProvider extends Provider {
|
||||
|
||||
void send(Map<String, String> config, UserModel user, String subject, String textBody, String htmlBody) throws EmailException;
|
||||
default void send(Map<String, String> config, UserModel user, String subject, String textBody, String htmlBody) throws EmailException {
|
||||
send(config, user.getEmail(), subject, textBody, htmlBody);
|
||||
}
|
||||
|
||||
void send(Map<String, String> config, String address, String subject, String textBody, String htmlBody) throws EmailException;
|
||||
}
|
||||
|
|
|
@ -77,6 +77,8 @@ public interface EmailTemplateProvider extends Provider {
|
|||
|
||||
void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException;
|
||||
|
||||
void sendEmailUpdateConfirmation(String link, long expirationInMinutes, String address) throws EmailException;
|
||||
|
||||
/**
|
||||
* Send formatted email
|
||||
*
|
||||
|
|
|
@ -28,6 +28,6 @@ public enum LoginFormsPages {
|
|||
LOGIN_PAGE_EXPIRED, CODE, X509_CONFIRM, SAML_POST_FORM,
|
||||
LOGIN_OAUTH2_DEVICE_VERIFY_USER_CODE, UPDATE_USER_PROFILE, IDP_REVIEW_USER_PROFILE,
|
||||
LOGIN_RECOVERY_AUTHN_CODES_INPUT, LOGIN_RECOVERY_AUTHN_CODES_CONFIG,
|
||||
FRONTCHANNEL_LOGOUT, LOGOUT_CONFIRM;
|
||||
FRONTCHANNEL_LOGOUT, LOGOUT_CONFIRM, UPDATE_EMAIL;
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.migration.MigrationProvider;
|
||||
import org.keycloak.migration.ModelVersion;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
|
|
@ -98,6 +98,7 @@ public class DefaultRequiredActions {
|
|||
|
||||
addUpdateLocaleAction(realm);
|
||||
addDeleteAccountAction(realm);
|
||||
addUpdateEmailAction(realm);
|
||||
}
|
||||
|
||||
public static void addDeleteAccountAction(RealmModel realm) {
|
||||
|
@ -125,4 +126,18 @@ public class DefaultRequiredActions {
|
|||
realm.addRequiredActionProvider(updateUserLocale);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addUpdateEmailAction(RealmModel realm){
|
||||
if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_EMAIL.name()) == null
|
||||
&& Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL)){
|
||||
RequiredActionProviderModel updateEmail = new RequiredActionProviderModel();
|
||||
updateEmail.setEnabled(true);
|
||||
updateEmail.setAlias(UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||
updateEmail.setName("Update Email");
|
||||
updateEmail.setProviderId(UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||
updateEmail.setDefaultAction(false);
|
||||
updateEmail.setPriority(70);
|
||||
realm.addRequiredActionProvider(updateEmail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,8 @@ public enum UserProfileContext {
|
|||
ACCOUNT_OLD(true),
|
||||
IDP_REVIEW(false),
|
||||
REGISTRATION_PROFILE(false),
|
||||
REGISTRATION_USER_CREATION(false);
|
||||
REGISTRATION_USER_CREATION(false),
|
||||
UPDATE_EMAIL(false);
|
||||
|
||||
protected boolean resetEmailVerified;
|
||||
|
||||
|
|
|
@ -305,7 +305,8 @@ public interface UserModel extends RoleMapperModel {
|
|||
CONFIGURE_RECOVERY_AUTHN_CODES,
|
||||
UPDATE_PASSWORD,
|
||||
TERMS_AND_CONDITIONS,
|
||||
VERIFY_PROFILE
|
||||
VERIFY_PROFILE,
|
||||
UPDATE_EMAIL
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.authentication.actiontoken.updateemail;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.keycloak.authentication.actiontoken.DefaultActionToken;
|
||||
|
||||
public class UpdateEmailActionToken extends DefaultActionToken {
|
||||
|
||||
public static final String TOKEN_TYPE = "update-email";
|
||||
|
||||
@JsonProperty("oldEmail")
|
||||
private String oldEmail;
|
||||
@JsonProperty("newEmail")
|
||||
private String newEmail;
|
||||
|
||||
public UpdateEmailActionToken(String userId, int absoluteExpirationInSecs, String oldEmail, String newEmail){
|
||||
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null);
|
||||
this.oldEmail = oldEmail;
|
||||
this.newEmail = newEmail;
|
||||
}
|
||||
|
||||
private UpdateEmailActionToken(){
|
||||
|
||||
}
|
||||
|
||||
public String getOldEmail() {
|
||||
return oldEmail;
|
||||
}
|
||||
|
||||
public void setOldEmail(String oldEmail) {
|
||||
this.oldEmail = oldEmail;
|
||||
}
|
||||
|
||||
public String getNewEmail() {
|
||||
return newEmail;
|
||||
}
|
||||
|
||||
public void setNewEmail(String newEmail) {
|
||||
this.newEmail = newEmail;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.authentication.actiontoken.updateemail;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.authentication.actiontoken.AbstractActionTokenHandler;
|
||||
import org.keycloak.authentication.actiontoken.ActionTokenContext;
|
||||
import org.keycloak.authentication.actiontoken.TokenUtils;
|
||||
import org.keycloak.authentication.requiredactions.UpdateEmail;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.userprofile.UserProfile;
|
||||
import org.keycloak.userprofile.ValidationException;
|
||||
|
||||
public class UpdateEmailActionTokenHandler extends AbstractActionTokenHandler<UpdateEmailActionToken> {
|
||||
|
||||
public UpdateEmailActionTokenHandler() {
|
||||
super(UpdateEmailActionToken.TOKEN_TYPE, UpdateEmailActionToken.class, Messages.STALE_VERIFY_EMAIL_LINK,
|
||||
EventType.EXECUTE_ACTIONS, Errors.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenVerifier.Predicate<? super UpdateEmailActionToken>[] getVerifiers(
|
||||
ActionTokenContext<UpdateEmailActionToken> tokenContext) {
|
||||
return TokenUtils.predicates(TokenUtils.checkThat(
|
||||
t -> Objects.equals(t.getOldEmail(), tokenContext.getAuthenticationSession().getAuthenticatedUser().getEmail()),
|
||||
Errors.INVALID_EMAIL, getDefaultErrorMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response handleToken(UpdateEmailActionToken token, ActionTokenContext<UpdateEmailActionToken> tokenContext) {
|
||||
AuthenticationSessionModel authenticationSession = tokenContext.getAuthenticationSession();
|
||||
UserModel user = authenticationSession.getAuthenticatedUser();
|
||||
|
||||
KeycloakSession session = tokenContext.getSession();
|
||||
|
||||
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authenticationSession)
|
||||
.setUser(user);
|
||||
|
||||
String newEmail = token.getNewEmail();
|
||||
|
||||
UserProfile emailUpdateValidationResult;
|
||||
try {
|
||||
emailUpdateValidationResult = UpdateEmail.validateEmailUpdate(session, user, newEmail);
|
||||
} catch (ValidationException pve) {
|
||||
List<FormMessage> errors = Validation.getFormErrorsFromValidation(pve.getErrors());
|
||||
return forms.setErrors(errors).createErrorPage(Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
UpdateEmail.updateEmailNow(tokenContext.getEvent(), user, emailUpdateValidationResult);
|
||||
|
||||
tokenContext.getEvent().success();
|
||||
|
||||
// verify user email as we know it is valid as this entry point would never have gotten here.
|
||||
user.setEmailVerified(true);
|
||||
user.removeRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL);
|
||||
tokenContext.getAuthenticationSession().removeRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL);
|
||||
user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
tokenContext.getAuthenticationSession().removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
|
||||
return forms.setAttribute("messageHeader", forms.getMessage("emailUpdatedTitle")).setSuccess("emailUpdated", newEmail)
|
||||
.createInfoPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseTokenRepeatedly(UpdateEmailActionToken token,
|
||||
ActionTokenContext<UpdateEmailActionToken> tokenContext) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -92,6 +92,12 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
|||
setSingleAttribute(UserModel.USERNAME, username);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isEditEmailAllowed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getModelUsername() {
|
||||
return getFirstAttribute(UserModel.USERNAME);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.authentication.requiredactions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.InitiatedActionSupport;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.authentication.actiontoken.updateemail.UpdateEmailActionToken;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailTemplateProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.LoginFormsPages;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.forms.login.freemarker.Templates;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||
import org.keycloak.userprofile.UserProfile;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.userprofile.ValidationException;
|
||||
|
||||
public class UpdateEmail implements RequiredActionProvider, RequiredActionFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(UpdateEmail.class);
|
||||
|
||||
@Override
|
||||
public InitiatedActionSupport initiatedActionSupport() {
|
||||
return InitiatedActionSupport.SUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Update Email";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
context.challenge(context.form().createResponse(UserModel.RequiredAction.UPDATE_EMAIL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
String newEmail = formData.getFirst(UserModel.EMAIL);
|
||||
|
||||
RealmModel realm = context.getRealm();
|
||||
UserModel user = context.getUser();
|
||||
UserProfile emailUpdateValidationResult;
|
||||
try {
|
||||
emailUpdateValidationResult = validateEmailUpdate(context.getSession(), user, newEmail);
|
||||
} catch (ValidationException pve) {
|
||||
List<FormMessage> errors = Validation.getFormErrorsFromValidation(pve.getErrors());
|
||||
context.challenge(context.form().setErrors(errors).setFormData(formData)
|
||||
.createResponse(UserModel.RequiredAction.UPDATE_EMAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!realm.isVerifyEmail() || Validation.isBlank(newEmail)
|
||||
|| Objects.equals(user.getEmail(), newEmail) && user.isEmailVerified()) {
|
||||
updateEmailWithoutConfirmation(context, emailUpdateValidationResult);
|
||||
return;
|
||||
}
|
||||
|
||||
sendEmailUpdateConfirmation(context);
|
||||
}
|
||||
|
||||
private void sendEmailUpdateConfirmation(RequiredActionContext context) {
|
||||
UserModel user = context.getUser();
|
||||
String oldEmail = user.getEmail();
|
||||
String newEmail = context.getHttpRequest().getDecodedFormParameters().getFirst(UserModel.EMAIL);
|
||||
|
||||
RealmModel realm = context.getRealm();
|
||||
int validityInSecs = realm.getActionTokenGeneratedByUserLifespan(UpdateEmailActionToken.TOKEN_TYPE);
|
||||
|
||||
UriInfo uriInfo = context.getUriInfo();
|
||||
KeycloakSession session = context.getSession();
|
||||
AuthenticationSessionModel authenticationSession = context.getAuthenticationSession();
|
||||
|
||||
UpdateEmailActionToken actionToken = new UpdateEmailActionToken(user.getId(), Time.currentTime() + validityInSecs,
|
||||
oldEmail, newEmail);
|
||||
|
||||
String link = Urls
|
||||
.actionTokenBuilder(uriInfo.getBaseUri(), actionToken.serialize(session, realm, uriInfo),
|
||||
authenticationSession.getClient().getClientId(), authenticationSession.getTabId())
|
||||
|
||||
.build(realm.getName()).toString();
|
||||
|
||||
context.getEvent().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, newEmail);
|
||||
try {
|
||||
session.getProvider(EmailTemplateProvider.class).setAuthenticationSession(authenticationSession).setRealm(realm)
|
||||
.setUser(user).sendEmailUpdateConfirmation(link, TimeUnit.SECONDS.toMinutes(validityInSecs), newEmail);
|
||||
} catch (EmailException e) {
|
||||
logger.error("Failed to send email for email update", e);
|
||||
context.getEvent().error(Errors.EMAIL_SEND_FAILED);
|
||||
return;
|
||||
}
|
||||
context.getEvent().success();
|
||||
|
||||
LoginFormsProvider forms = context.form();
|
||||
context.challenge(forms.setAttribute("messageHeader", forms.getMessage("emailUpdateConfirmationSentTitle"))
|
||||
.setInfo("emailUpdateConfirmationSent", newEmail).createForm(Templates.getTemplate(LoginFormsPages.INFO)));
|
||||
}
|
||||
|
||||
private void updateEmailWithoutConfirmation(RequiredActionContext context,
|
||||
UserProfile emailUpdateValidationResult) {
|
||||
|
||||
updateEmailNow(context.getEvent(), context.getUser(), emailUpdateValidationResult);
|
||||
context.success();
|
||||
}
|
||||
|
||||
public static UserProfile validateEmailUpdate(KeycloakSession session, UserModel user, String newEmail) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedHashMap<>();
|
||||
formData.putSingle(UserModel.EMAIL, newEmail);
|
||||
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
||||
UserProfile profile = profileProvider.create(UserProfileContext.UPDATE_EMAIL, formData, user);
|
||||
profile.validate();
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static void updateEmailNow(EventBuilder event, UserModel user, UserProfile emailUpdateValidationResult) {
|
||||
|
||||
String oldEmail = user.getEmail();
|
||||
String newEmail = emailUpdateValidationResult.getAttributes().getFirstValue(UserModel.EMAIL);
|
||||
event.event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, newEmail);
|
||||
emailUpdateValidationResult.update(false, new EventAuditingAttributeChangeListener(emailUpdateValidationResult, event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return UserModel.RequiredAction.UPDATE_EMAIL.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL);
|
||||
}
|
||||
}
|
|
@ -40,6 +40,8 @@ public interface UpdateProfileContext {
|
|||
|
||||
void setUsername(String username);
|
||||
|
||||
boolean isEditEmailAllowed();
|
||||
|
||||
String getEmail();
|
||||
|
||||
void setEmail(String email);
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
|
||||
package org.keycloak.authentication.requiredactions.util;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -58,6 +58,11 @@ public class UserUpdateProfileContext implements UpdateProfileContext {
|
|||
user.setUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditEmailAllowed() {
|
||||
return !Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return user.getEmail();
|
||||
|
|
|
@ -61,9 +61,13 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
|
|||
|
||||
@Override
|
||||
public void send(Map<String, String> config, UserModel user, String subject, String textBody, String htmlBody) throws EmailException {
|
||||
send(config, retrieveEmailAddress(user), subject, textBody, htmlBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Map<String, String> config, String address, String subject, String textBody, String htmlBody) throws EmailException {
|
||||
Transport transport = null;
|
||||
try {
|
||||
String address = retrieveEmailAddress(user);
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
|
|
|
@ -181,6 +181,22 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
|
|||
send("emailVerificationSubject", "email-verification.ftl", attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEmailUpdateConfirmation(String link, long expirationInMinutes, String newEmail) throws EmailException {
|
||||
if (newEmail == null) {
|
||||
throw new IllegalArgumentException("The new email is mandatory");
|
||||
}
|
||||
|
||||
Map<String, Object> attributes = new HashMap<>(this.attributes);
|
||||
attributes.put("user", new ProfileBean(user));
|
||||
attributes.put("newEmail", newEmail);
|
||||
addLinkInfoIntoAttributes(link, expirationInMinutes, attributes);
|
||||
|
||||
attributes.put("realmName", getRealmName());
|
||||
|
||||
send("emailUpdateConfirmationSubject", Collections.emptyList(), "email-update-confirmation.ftl", attributes, newEmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add link info into template attributes.
|
||||
*
|
||||
|
@ -245,9 +261,13 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
|
|||
|
||||
@Override
|
||||
public void send(String subjectFormatKey, List<Object> subjectAttributes, String bodyTemplate, Map<String, Object> bodyAttributes) throws EmailException {
|
||||
send(subjectFormatKey, subjectAttributes, bodyTemplate, bodyAttributes, null);
|
||||
}
|
||||
|
||||
protected void send(String subjectFormatKey, List<Object> subjectAttributes, String bodyTemplate, Map<String, Object> bodyAttributes, String address) throws EmailException {
|
||||
try {
|
||||
EmailTemplate email = processTemplate(subjectFormatKey, subjectAttributes, bodyTemplate, bodyAttributes);
|
||||
send(email.getSubject(), email.getTextBody(), email.getHtmlBody());
|
||||
send(email.getSubject(), email.getTextBody(), email.getHtmlBody(), address);
|
||||
} catch (EmailException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
|
@ -255,13 +275,21 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
|
|||
}
|
||||
}
|
||||
|
||||
protected void send(String subject, String textBody, String htmlBody) throws EmailException {
|
||||
send(realm.getSmtpConfig(), subject, textBody, htmlBody);
|
||||
protected void send(String subject, String textBody, String htmlBody, String address) throws EmailException {
|
||||
send(realm.getSmtpConfig(), subject, textBody, htmlBody, address);
|
||||
}
|
||||
|
||||
protected void send(Map<String, String> config, String subject, String textBody, String htmlBody) throws EmailException {
|
||||
send(config, subject, textBody, htmlBody, null);
|
||||
}
|
||||
|
||||
protected void send(Map<String, String> config, String subject, String textBody, String htmlBody, String address) throws EmailException {
|
||||
EmailSenderProvider emailSender = session.getProvider(EmailSenderProvider.class);
|
||||
if (address == null) {
|
||||
emailSender.send(config, user, subject, textBody, htmlBody);
|
||||
} else {
|
||||
emailSender.send(config, address, subject, textBody, htmlBody);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,24 @@
|
|||
*/
|
||||
package org.keycloak.forms.login.freemarker;
|
||||
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
|
@ -32,6 +50,7 @@ import org.keycloak.forms.login.freemarker.model.RecoveryAuthnCodeInputLoginBean
|
|||
import org.keycloak.forms.login.freemarker.model.RecoveryAuthnCodesBean;
|
||||
import org.keycloak.forms.login.freemarker.model.ClientBean;
|
||||
import org.keycloak.forms.login.freemarker.model.CodeBean;
|
||||
import org.keycloak.forms.login.freemarker.model.EmailBean;
|
||||
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
||||
import org.keycloak.forms.login.freemarker.model.IdpReviewProfileBean;
|
||||
import org.keycloak.forms.login.freemarker.model.LoginBean;
|
||||
|
@ -74,25 +93,6 @@ import org.keycloak.userprofile.UserProfileContext;
|
|||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -159,8 +159,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
page = LoginFormsPages.LOGIN_RECOVERY_AUTHN_CODES_CONFIG;
|
||||
break;
|
||||
case UPDATE_PROFILE:
|
||||
UpdateProfileContext userBasedContext = new UserUpdateProfileContext(realm, user);
|
||||
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, userBasedContext);
|
||||
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, new UserUpdateProfileContext(realm, user));
|
||||
|
||||
actionMessage = Messages.UPDATE_PROFILE;
|
||||
if(isDynamicUserProfile()) {
|
||||
|
@ -169,6 +168,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||
}
|
||||
break;
|
||||
case UPDATE_EMAIL:
|
||||
actionMessage = Messages.UPDATE_EMAIL;
|
||||
page = LoginFormsPages.UPDATE_EMAIL;
|
||||
break;
|
||||
case UPDATE_PASSWORD:
|
||||
boolean isRequestedByAdmin = user.getRequiredActionsStream().filter(Objects::nonNull).anyMatch(UPDATE_PASSWORD.toString()::contains);
|
||||
actionMessage = isRequestedByAdmin ? Messages.UPDATE_PASSWORD : Messages.RESET_PASSWORD;
|
||||
|
@ -237,6 +240,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||
attributes.put("user", new ProfileBean(userCtx, formData));
|
||||
break;
|
||||
case UPDATE_EMAIL:
|
||||
attributes.put("email", new EmailBean(user, formData));
|
||||
break;
|
||||
case LOGIN_IDP_LINK_CONFIRM:
|
||||
case LOGIN_IDP_LINK_EMAIL:
|
||||
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
|
||||
|
|
|
@ -70,6 +70,8 @@ public class Templates {
|
|||
return "webauthn-error.ftl";
|
||||
case LOGIN_UPDATE_PROFILE:
|
||||
return "login-update-profile.ftl";
|
||||
case UPDATE_EMAIL:
|
||||
return "update-email.ftl";
|
||||
case CODE:
|
||||
return "code.ftl";
|
||||
case LOGIN_PAGE_EXPIRED:
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.forms.login.freemarker.model;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
public class EmailBean {
|
||||
|
||||
private final UserModel user;
|
||||
private final MultivaluedMap<String, String> formData;
|
||||
|
||||
public EmailBean(UserModel user, MultivaluedMap<String, String> formData) {
|
||||
this.user = user;
|
||||
this.formData = formData;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return formData != null ? formData.getFirst("email") : user.getEmail();
|
||||
}
|
||||
}
|
|
@ -69,6 +69,10 @@ public class ProfileBean {
|
|||
return user.isEditUsernameAllowed();
|
||||
}
|
||||
|
||||
public boolean isEditEmailAllowed() {
|
||||
return user.isEditEmailAllowed();
|
||||
}
|
||||
|
||||
public String getUsername() { return formData != null ? formData.getFirst("username") : user.getUsername(); }
|
||||
|
||||
public String getFirstName() {
|
||||
|
|
|
@ -102,6 +102,8 @@ public class Messages {
|
|||
|
||||
public static final String VERIFY_EMAIL = "verifyEmailMessage";
|
||||
|
||||
public static final String UPDATE_EMAIL = "updateEmailMessage";
|
||||
|
||||
public static final String LINK_IDP = "linkIdpMessage";
|
||||
|
||||
public static final String EMAIL_VERIFIED = "emailVerifiedMessage";
|
||||
|
|
|
@ -1,38 +1,5 @@
|
|||
package org.keycloak.services.resources.account;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.authentication.requiredactions.DeleteAccount;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.ResolveRelative;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.theme.FreeMarkerException;
|
||||
import org.keycloak.theme.FreeMarkerUtil;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.beans.MessageFormatterMethod;
|
||||
import org.keycloak.urls.UrlType;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
|
@ -45,9 +12,42 @@ import java.util.function.Function;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.authentication.requiredactions.DeleteAccount;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.util.ResolveRelative;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.theme.FreeMarkerException;
|
||||
import org.keycloak.theme.FreeMarkerUtil;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.beans.MessageFormatterMethod;
|
||||
import org.keycloak.urls.UrlType;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
/**
|
||||
* Created by st on 29/03/17.
|
||||
|
@ -144,6 +144,9 @@ public class AccountConsole {
|
|||
map.put("isTotpConfigured", isTotpConfigured);
|
||||
|
||||
map.put("deleteAccountAllowed", deleteAccountAllowed);
|
||||
map.put("updateEmailFeatureEnabled", Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL));
|
||||
RequiredActionProviderModel updateEmailActionProvider = realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||
map.put("updateEmailActionEnabled", updateEmailActionProvider != null && updateEmailActionProvider.isEnabled());
|
||||
|
||||
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
|
||||
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
|
||||
|
|
|
@ -25,6 +25,7 @@ import static org.keycloak.userprofile.UserProfileContext.ACCOUNT_OLD;
|
|||
import static org.keycloak.userprofile.UserProfileContext.IDP_REVIEW;
|
||||
import static org.keycloak.userprofile.UserProfileContext.REGISTRATION_PROFILE;
|
||||
import static org.keycloak.userprofile.UserProfileContext.REGISTRATION_USER_CREATION;
|
||||
import static org.keycloak.userprofile.UserProfileContext.UPDATE_EMAIL;
|
||||
import static org.keycloak.userprofile.UserProfileContext.UPDATE_PROFILE;
|
||||
import static org.keycloak.userprofile.UserProfileContext.USER_API;
|
||||
|
||||
|
@ -36,8 +37,8 @@ import java.util.Map;
|
|||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -57,7 +58,6 @@ import org.keycloak.userprofile.validator.UsernameHasValueValidator;
|
|||
import org.keycloak.userprofile.validator.UsernameIDNHomographValidator;
|
||||
import org.keycloak.userprofile.validator.UsernameMutationValidator;
|
||||
import org.keycloak.validate.ValidatorConfig;
|
||||
import org.keycloak.validate.validators.EmailValidator;
|
||||
|
||||
/**
|
||||
* <p>A base class for {@link UserProfileProvider} implementations providing the main hooks for customizations.
|
||||
|
@ -97,11 +97,21 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
return !realm.isRegistrationEmailAsUsername();
|
||||
case UPDATE_PROFILE:
|
||||
return realm.isEditUsernameAllowed();
|
||||
case UPDATE_EMAIL:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean editEmailCondition(AttributeContext c) {
|
||||
return !Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL) || (c.getContext() != UPDATE_PROFILE && c.getContext() != ACCOUNT);
|
||||
}
|
||||
|
||||
private static boolean readEmailCondition(AttributeContext c) {
|
||||
return !Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL) || c.getContext() != UPDATE_PROFILE;
|
||||
}
|
||||
|
||||
public static Pattern getRegexPatternString(String[] builtinReadOnlyAttributes) {
|
||||
if (builtinReadOnlyAttributes != null) {
|
||||
List<String> readOnlyAttributes = new ArrayList<>(Arrays.asList(builtinReadOnlyAttributes));
|
||||
|
@ -177,6 +187,9 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(ACCOUNT_OLD, readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(REGISTRATION_PROFILE, readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(UPDATE_PROFILE, readOnlyValidator)));
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL)) {
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(UPDATE_EMAIL, readOnlyValidator)));
|
||||
}
|
||||
addContextualProfileMetadata(configureUserProfile(createRegistrationUserCreationProfile()));
|
||||
addContextualProfileMetadata(configureUserProfile(createUserResourceValidation(config)));
|
||||
}
|
||||
|
@ -305,6 +318,8 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
new AttributeValidatorMetadata(UsernameMutationValidator.ID)).setAttributeDisplayName("${username}");
|
||||
|
||||
metadata.addAttribute(UserModel.EMAIL, -1,
|
||||
AbstractUserProfileProvider::editEmailCondition,
|
||||
AbstractUserProfileProvider::readEmailCondition,
|
||||
new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, false)),
|
||||
new AttributeValidatorMetadata(DuplicateEmailValidator.ID),
|
||||
new AttributeValidatorMetadata(EmailExistsAsUsernameValidator.ID))
|
||||
|
|
|
@ -26,3 +26,4 @@ org.keycloak.authentication.requiredactions.UpdateUserLocaleAction
|
|||
org.keycloak.authentication.requiredactions.DeleteAccount
|
||||
org.keycloak.authentication.requiredactions.VerifyUserProfile
|
||||
org.keycloak.authentication.requiredactions.RecoveryAuthnCodesAction
|
||||
org.keycloak.authentication.requiredactions.UpdateEmail
|
||||
|
|
|
@ -2,3 +2,4 @@ org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHan
|
|||
org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionTokenHandler
|
||||
org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionTokenHandler
|
||||
org.keycloak.authentication.actiontoken.idpverifyemail.IdpVerifyAccountLinkActionTokenHandler
|
||||
org.keycloak.authentication.actiontoken.updateemail.UpdateEmailActionTokenHandler
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
public class UpdateEmailPage extends RequiredActions {
|
||||
|
||||
@FindBy(id = "email")
|
||||
private WebElement emailInput;
|
||||
|
||||
@FindBy(id = "input-error-email")
|
||||
private WebElement inputErrorEmail;
|
||||
|
||||
@FindBy(css = "button[name='cancel-aia']")
|
||||
private WebElement cancelActionButton;
|
||||
|
||||
@FindBy(css = "input[type='submit']")
|
||||
private WebElement submitActionButton;
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return UserModel.RequiredAction.UPDATE_EMAIL.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return driver.getCurrentUrl().contains("login-actions/required-action")
|
||||
&& driver.getCurrentUrl().contains("execution=" + getActionId());
|
||||
}
|
||||
|
||||
public void changeEmail(String email){
|
||||
emailInput.clear();
|
||||
emailInput.sendKeys(email);
|
||||
|
||||
submit();
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return emailInput.getAttribute("value");
|
||||
}
|
||||
|
||||
public String getEmailInputError() {
|
||||
try {
|
||||
return getTextFromElement(inputErrorEmail);
|
||||
} catch (NoSuchElementException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCancelDisplayed() {
|
||||
try {
|
||||
return cancelActionButton.isDisplayed();
|
||||
} catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void clickCancelAIA() {
|
||||
clickLink(cancelActionButton);
|
||||
}
|
||||
|
||||
public void clickSubmitAction() {
|
||||
clickLink(submitActionButton);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.pages;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
import static org.keycloak.testsuite.util.UIUtils.isElementVisible;
|
||||
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
public class EmailUpdatePage extends AbstractPage {
|
||||
|
||||
@FindBy(id = "email")
|
||||
private WebElement emailInput;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(name = "cancel-aia")
|
||||
private WebElement cancelAIAButton;
|
||||
|
||||
@FindBy(id = "input-error-email")
|
||||
private WebElement emailError;
|
||||
|
||||
public void changeEmail(String newEmail) {
|
||||
emailInput.clear();
|
||||
emailInput.sendKeys(newEmail);
|
||||
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelAIAButton.click();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return PageUtils.getPageTitle(driver).equals("Update email");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public String getEmailError() {
|
||||
try {
|
||||
return getTextFromElement(emailError);
|
||||
} catch (NoSuchElementException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCancelDisplayed() {
|
||||
return isElementVisible(cancelAIAButton);
|
||||
}
|
||||
}
|
|
@ -26,16 +26,8 @@ public class LoginUpdateProfileEditUsernameAllowedPage extends LoginUpdateProfil
|
|||
@FindBy(id = "username")
|
||||
private WebElement usernameInput;
|
||||
|
||||
public void update(String firstName, String lastName, String email, String username) {
|
||||
usernameInput.clear();
|
||||
usernameInput.sendKeys(username);
|
||||
update(firstName, lastName, email);
|
||||
}
|
||||
|
||||
public void updateWithDepartment(String firstName, String lastName, String department, String email, String username) {
|
||||
usernameInput.clear();
|
||||
usernameInput.sendKeys(username);
|
||||
super.updateWithDepartment(firstName, lastName, department, email);
|
||||
public Update prepareUpdate() {
|
||||
return new Update(this);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
|
@ -59,4 +51,29 @@ public class LoginUpdateProfileEditUsernameAllowedPage extends LoginUpdateProfil
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static class Update extends LoginUpdateProfilePage.Update {
|
||||
|
||||
private final LoginUpdateProfileEditUsernameAllowedPage page;
|
||||
private String username;
|
||||
|
||||
protected Update(LoginUpdateProfileEditUsernameAllowedPage page) {
|
||||
super(page);
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Update username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit() {
|
||||
if (username != null) {
|
||||
page.usernameInput.clear();
|
||||
page.usernameInput.sendKeys(username);
|
||||
}
|
||||
super.submit();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.keycloak.testsuite.pages;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
|
@ -24,9 +27,6 @@ import org.openqa.selenium.NoSuchElementException;
|
|||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -56,30 +56,16 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
|||
@FindBy(className = "alert-error")
|
||||
private WebElement loginAlertErrorMessage;
|
||||
|
||||
public void update(String firstName, String lastName) {
|
||||
prepareUpdate().firstName(firstName).lastName(lastName).submit();
|
||||
}
|
||||
|
||||
public void update(String firstName, String lastName, String email) {
|
||||
updateWithDepartment(firstName, lastName, null, email);
|
||||
prepareUpdate().firstName(firstName).lastName(lastName).email(email).submit();
|
||||
}
|
||||
|
||||
public void updateWithDepartment(String firstName, String lastName, String department, String email) {
|
||||
if (firstName != null) {
|
||||
firstNameInput.clear();
|
||||
firstNameInput.sendKeys(firstName);
|
||||
}
|
||||
if (lastName != null) {
|
||||
lastNameInput.clear();
|
||||
lastNameInput.sendKeys(lastName);
|
||||
}
|
||||
if (email != null) {
|
||||
emailInput.clear();
|
||||
emailInput.sendKeys(email);
|
||||
}
|
||||
|
||||
if(department != null) {
|
||||
departmentInput.clear();
|
||||
departmentInput.sendKeys(department);
|
||||
}
|
||||
|
||||
clickLink(submitButton);
|
||||
public Update prepareUpdate() {
|
||||
return new Update(this);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
|
@ -148,6 +134,61 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Update {
|
||||
private final LoginUpdateProfilePage page;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String department;
|
||||
private String email;
|
||||
|
||||
protected Update(LoginUpdateProfilePage page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Update firstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Update lastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Update department(String department) {
|
||||
this.department = department;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Update email(String email) {
|
||||
this.email = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void submit() {
|
||||
if (firstName != null) {
|
||||
page.firstNameInput.clear();
|
||||
page.firstNameInput.sendKeys(firstName);
|
||||
}
|
||||
if (lastName != null) {
|
||||
page.lastNameInput.clear();
|
||||
page.lastNameInput.sendKeys(lastName);
|
||||
}
|
||||
|
||||
if(department != null) {
|
||||
page.departmentInput.clear();
|
||||
page.departmentInput.sendKeys(department);
|
||||
}
|
||||
|
||||
if (email != null) {
|
||||
page.emailInput.clear();
|
||||
page.emailInput.sendKeys(email);
|
||||
}
|
||||
|
||||
clickLink(page.submitButton);
|
||||
}
|
||||
}
|
||||
|
||||
// For managing input errors
|
||||
public static class UpdateProfileErrors {
|
||||
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.pages.EmailUpdatePage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
@EnableFeature(Profile.Feature.UPDATE_EMAIL)
|
||||
public abstract class AbstractAppInitiatedActionUpdateEmailTest extends AbstractAppInitiatedActionTest {
|
||||
|
||||
@Page
|
||||
protected EmailUpdatePage emailUpdatePage;
|
||||
|
||||
@Override
|
||||
protected String getAiaAction() {
|
||||
return UserModel.RequiredAction.UPDATE_EMAIL.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeTest() {
|
||||
ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost");
|
||||
UserRepresentation user = UserBuilder.create().enabled(true).username("test-user@localhost")
|
||||
.email("test-user@localhost").firstName("Tom").lastName("Brady").build();
|
||||
prepareUser(user);
|
||||
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
|
||||
|
||||
ApiUtil.removeUserByUsername(testRealm(), "john-doh@localhost");
|
||||
user = UserBuilder.create().enabled(true).username("john-doh@localhost").email("john-doh@localhost").firstName("John")
|
||||
.lastName("Doh").build();
|
||||
prepareUser(user);
|
||||
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
|
||||
}
|
||||
|
||||
protected void prepareUser(UserRepresentation user){
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelUpdateEmail() {
|
||||
doAIA();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
emailUpdatePage.cancel();
|
||||
|
||||
assertKcActionStatus("cancelled");
|
||||
|
||||
// assert nothing was updated in persistent store
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
Assert.assertEquals("test-user@localhost", user.getEmail());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateToExistingEmail() {
|
||||
doAIA();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
emailUpdatePage.changeEmail("john-doh@localhost");
|
||||
emailUpdatePage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Email already exists.", emailUpdatePage.getEmailError());
|
||||
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
Assert.assertEquals("test-user@localhost", user.getEmail());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateToInvalidEmail(){
|
||||
doAIA();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
emailUpdatePage.changeEmail("invalidemail");
|
||||
emailUpdatePage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Invalid email address.", emailUpdatePage.getEmailError());
|
||||
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
Assert.assertEquals("test-user@localhost", user.getEmail());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateToBlankEmail(){
|
||||
doAIA();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
emailUpdatePage.changeEmail("");
|
||||
emailUpdatePage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Please specify email.", emailUpdatePage.getEmailError());
|
||||
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
Assert.assertEquals("test-user@localhost", user.getEmail());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdateEmailPage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
@EnableFeature(Profile.Feature.UPDATE_EMAIL)
|
||||
public abstract class AbstractRequiredActionUpdateEmailTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected UpdateEmailPage updateEmailPage;
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Before
|
||||
public void beforeTest() {
|
||||
ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost");
|
||||
UserRepresentation user = UserBuilder.create().enabled(true)
|
||||
.username("test-user@localhost")
|
||||
.email("test-user@localhost")
|
||||
.firstName("Tom")
|
||||
.lastName("Brady")
|
||||
.requiredAction(UserModel.RequiredAction.UPDATE_EMAIL.name()).build();
|
||||
prepareUser(user);
|
||||
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
|
||||
|
||||
ApiUtil.removeUserByUsername(testRealm(), "john-doh@localhost");
|
||||
user = UserBuilder.create().enabled(true)
|
||||
.username("john-doh@localhost")
|
||||
.email("john-doh@localhost")
|
||||
.firstName("John")
|
||||
.lastName("Doh")
|
||||
.requiredAction(UserModel.RequiredAction.UPDATE_EMAIL.name()).build();
|
||||
prepareUser(user);
|
||||
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
|
||||
}
|
||||
|
||||
protected void prepareUser(UserRepresentation user){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelIsNotDisplayed(){
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
assertFalse(updateEmailPage.isCancelDisplayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailMissing() {
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
updateEmailPage.changeEmail("");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
// assert that form holds submitted values during validation error
|
||||
Assert.assertEquals("", updateEmailPage.getEmail());
|
||||
|
||||
Assert.assertEquals("Please specify email.", updateEmailPage.getEmailInputError());
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailDuplicate() {
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("john-doh@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
updateEmailPage.changeEmail("test-user@localhost");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
// assert that form holds submitted values during validation error
|
||||
Assert.assertEquals("test-user@localhost", updateEmailPage.getEmail());
|
||||
|
||||
Assert.assertEquals("Email already exists.", updateEmailPage.getEmailInputError());
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailInvalid() {
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
updateEmailPage.changeEmail("invalid");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
// assert that form holds submitted values during validation error
|
||||
Assert.assertEquals("invalid", updateEmailPage.getEmail());
|
||||
|
||||
Assert.assertEquals("Invalid email address.", updateEmailPage.getEmailInputError());
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
||||
public class AppInitiatedActionUpdateEmailTest extends AbstractAppInitiatedActionUpdateEmailTest {
|
||||
|
||||
@Test
|
||||
public void updateEmail() {
|
||||
doAIA();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
assertTrue(emailUpdatePage.isCancelDisplayed());
|
||||
|
||||
emailUpdatePage.changeEmail("new@email.com");
|
||||
|
||||
events.expect(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
|
||||
assertKcActionStatus("success");
|
||||
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
Assert.assertEquals("new@email.com", user.getEmail());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.mail.Address;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.InfoPage;
|
||||
import org.keycloak.testsuite.util.GreenMailRule;
|
||||
import org.keycloak.testsuite.util.MailUtils;
|
||||
|
||||
public class AppInitiatedActionUpdateEmailWithVerificationTest extends AbstractAppInitiatedActionUpdateEmailTest {
|
||||
|
||||
@Rule
|
||||
public GreenMailRule greenMail = new GreenMailRule();
|
||||
|
||||
@Page
|
||||
protected InfoPage infoPage;
|
||||
|
||||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
testRealm.setVerifyEmail(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareUser(UserRepresentation user) {
|
||||
user.setEmailVerified(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmail() throws IOException, MessagingException {
|
||||
doAIA();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
assertTrue(emailUpdatePage.isCancelDisplayed());
|
||||
emailUpdatePage.changeEmail("new@localhost");
|
||||
|
||||
events.expect(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, "new@localhost").assertEvent();
|
||||
Assert.assertEquals("test-user@localhost", ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost").getEmail());
|
||||
|
||||
driver.navigate().to(fetchEmailConfirmationLink("new@localhost"));
|
||||
|
||||
infoPage.assertCurrent();
|
||||
assertEquals("The account email has been successfully updated to new@localhost.", infoPage.getInfo());
|
||||
|
||||
events.expect(EventType.UPDATE_EMAIL)
|
||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||
.detail(Details.UPDATED_EMAIL, "new@localhost");
|
||||
|
||||
Assert.assertEquals("new@localhost", ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost").getEmail());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmEmailUpdateAfterThirdPartyEmailUpdate() throws MessagingException, IOException {
|
||||
doAIA();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
emailUpdatePage.changeEmail("new@localhost");
|
||||
|
||||
String confirmationLink = fetchEmailConfirmationLink("new@localhost");
|
||||
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
user.setEmail("very-new@localhost");
|
||||
user.setEmailVerified(true);
|
||||
testRealm().users().get(user.getId()).update(user);
|
||||
|
||||
driver.navigate().to(confirmationLink);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmEmailAfterDuplicateEmailSetForThirdPartyAccount() throws MessagingException, IOException {
|
||||
doAIA();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
emailUpdatePage.assertCurrent();
|
||||
emailUpdatePage.changeEmail("new@localhost");
|
||||
|
||||
String confirmationLink = fetchEmailConfirmationLink("new@localhost");
|
||||
|
||||
UserRepresentation otherUser = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost");
|
||||
otherUser.setEmail("new@localhost");
|
||||
otherUser.setEmailVerified(true);
|
||||
testRealm().users().get(otherUser.getId()).update(otherUser);
|
||||
|
||||
driver.navigate().to(confirmationLink);
|
||||
|
||||
errorPage.assertCurrent();
|
||||
assertEquals("Email already exists.", errorPage.getError());
|
||||
}
|
||||
|
||||
private String fetchEmailConfirmationLink(String emailRecipient) throws MessagingException, IOException {
|
||||
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
|
||||
Assert.assertEquals(1, receivedMessages.length);
|
||||
MimeMessage message = receivedMessages[0];
|
||||
Address[] recipients = message.getRecipients(Message.RecipientType.TO);
|
||||
Assert.assertTrue(recipients.length >= 1);
|
||||
assertEquals(emailRecipient, recipients[0].toString());
|
||||
|
||||
return MailUtils.getPasswordResetEmailLink(message).trim();
|
||||
}
|
||||
|
||||
}
|
|
@ -85,7 +85,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last")
|
||||
|
@ -115,7 +115,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
|
@ -165,7 +165,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "john-doh@localhost", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("New last").email("john-doh@localhost").submit();
|
||||
|
||||
events.expectLogin()
|
||||
.event(EventType.UPDATE_PROFILE)
|
||||
|
@ -199,7 +199,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("", "New last", "new@email.com", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -224,7 +224,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "", "new@email.com", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -249,7 +249,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("New last").email("").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -271,7 +271,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "invalidemail", "invalid");
|
||||
updateProfilePage.prepareUpdate().username("invalid").firstName("New first").lastName("New last").email("invalidemail").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -293,7 +293,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "");
|
||||
updateProfilePage.prepareUpdate().username("").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -316,7 +316,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -339,7 +339,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("keycloak-user@localhost").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -363,7 +363,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
|
|||
// Expire cookies and assert the page with "back to application" link present
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("keycloak-user@localhost").submit();
|
||||
errorPage.assertCurrent();
|
||||
|
||||
String backToAppLink = errorPage.getBackToApplicationLink();
|
||||
|
|
|
@ -94,7 +94,8 @@ public class RequiredActionMultipleActionsTest extends AbstractTestRealmKeycloak
|
|||
}
|
||||
|
||||
public String updateProfile(String codeId) {
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last")
|
||||
.email("new@email.com").submit();
|
||||
|
||||
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PROFILE)
|
||||
.detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
@ -43,11 +48,6 @@ import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
|||
import org.keycloak.testsuite.pages.TermsAndConditionsPage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a>
|
||||
*/
|
||||
|
@ -116,7 +116,7 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// Finally, update profile
|
||||
updateProfilePage.assertCurrent();
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
.detail(Details.UPDATED_LAST_NAME, "New last")
|
||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||
|
@ -150,7 +150,7 @@ public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// Second, update profile
|
||||
updateProfilePage.assertCurrent();
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
.detail(Details.UPDATED_LAST_NAME, "New last")
|
||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
|
||||
public class RequiredActionUpdateEmailTest extends AbstractRequiredActionUpdateEmailTest {
|
||||
|
||||
@Test
|
||||
public void updateEmail() {
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
|
||||
updateEmailPage.changeEmail("new-email@localhost");
|
||||
|
||||
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||
.detail(Details.UPDATED_EMAIL, "new-email@localhost").assertEvent();
|
||||
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
|
||||
// assert user is really updated in persistent store
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
assertEquals("new-email@localhost", user.getEmail());
|
||||
assertFalse(user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.mail.Address;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.InfoPage;
|
||||
import org.keycloak.testsuite.util.GreenMailRule;
|
||||
import org.keycloak.testsuite.util.MailUtils;
|
||||
|
||||
public class RequiredActionUpdateEmailTestWithVerificationTest extends AbstractRequiredActionUpdateEmailTest {
|
||||
|
||||
@Rule
|
||||
public GreenMailRule greenMail = new GreenMailRule();
|
||||
|
||||
@Page
|
||||
private InfoPage infoPage;
|
||||
|
||||
@Page
|
||||
private ErrorPage errorPage;
|
||||
|
||||
protected void prepareUser(UserRepresentation user){
|
||||
user.setEmailVerified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
testRealm.setVerifyEmail(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmail() throws IOException, MessagingException {
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
updateEmailPage.changeEmail("new@localhost");
|
||||
|
||||
events.expect(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, "new@localhost").assertEvent();
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
assertEquals("test-user@localhost", user.getEmail());
|
||||
assertTrue(user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name()));
|
||||
|
||||
driver.navigate().to(fetchEmailConfirmationLink("new@localhost"));
|
||||
|
||||
infoPage.assertCurrent();
|
||||
assertEquals("The account email has been successfully updated to new@localhost.", infoPage.getInfo());
|
||||
|
||||
events.expect(EventType.UPDATE_EMAIL)
|
||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||
.detail(Details.UPDATED_EMAIL, "new@localhost");
|
||||
|
||||
user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
assertEquals("new@localhost", user.getEmail());
|
||||
assertFalse(user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmEmailUpdateAfterThirdPartyEmailUpdate() throws MessagingException, IOException {
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
updateEmailPage.changeEmail("new@localhost");
|
||||
|
||||
String confirmationLink = fetchEmailConfirmationLink("new@localhost");
|
||||
|
||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||
user.setEmail("very-new@localhost");
|
||||
user.setEmailVerified(true);
|
||||
testRealm().users().get(user.getId()).update(user);
|
||||
|
||||
driver.navigate().to(confirmationLink);
|
||||
|
||||
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());
|
||||
assertTrue(ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost").getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmEmailAfterDuplicateEmailSetForThirdPartyAccount() throws MessagingException, IOException {
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
updateEmailPage.assertCurrent();
|
||||
updateEmailPage.changeEmail("new@localhost");
|
||||
|
||||
String confirmationLink = fetchEmailConfirmationLink("new@localhost");
|
||||
|
||||
UserRepresentation otherUser = ActionUtil.findUserWithAdminClient(adminClient, "john-doh@localhost");
|
||||
otherUser.setEmail("new@localhost");
|
||||
otherUser.setEmailVerified(true);
|
||||
testRealm().users().get(otherUser.getId()).update(otherUser);
|
||||
|
||||
driver.navigate().to(confirmationLink);
|
||||
|
||||
errorPage.assertCurrent();
|
||||
assertEquals("Email already exists.", errorPage.getError());
|
||||
assertTrue(ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost").getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name()));
|
||||
}
|
||||
|
||||
private String fetchEmailConfirmationLink(String emailRecipient) throws MessagingException, IOException {
|
||||
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
|
||||
assertEquals(1, receivedMessages.length);
|
||||
MimeMessage message = receivedMessages[0];
|
||||
Address[] recipients = message.getRecipients(Message.RecipientType.TO);
|
||||
assertTrue(recipients.length >= 1);
|
||||
assertEquals(emailRecipient, recipients[0].toString());
|
||||
|
||||
return MailUtils.getPasswordResetEmailLink(message).trim();
|
||||
}
|
||||
}
|
|
@ -16,6 +16,10 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
|
@ -29,8 +33,8 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
|
@ -40,11 +44,6 @@ import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
|||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -107,7 +106,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
updateProfilePage.assertCurrent();
|
||||
assertFalse(updateProfilePage.isCancelDisplayed());
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last")
|
||||
|
@ -137,7 +136,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "john-doh@localhost", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("New last").email("john-doh@localhost").submit();
|
||||
|
||||
events.expectLogin().event(EventType.UPDATE_PROFILE).detail(Details.UPDATED_FIRST_NAME, "New first").user(userId).session(Matchers.nullValue(String.class)).removeDetail(Details.CONSENT)
|
||||
.detail(Details.UPDATED_LAST_NAME, "New last").user(userId).session(Matchers.nullValue(String.class)).removeDetail(Details.CONSENT)
|
||||
|
@ -167,7 +166,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("", "New last", "new@email.com", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -192,7 +191,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "", "new@email.com", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -217,7 +216,8 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "", "new");
|
||||
updateProfilePage.prepareUpdate().username("new").firstName("New first").lastName("New last")
|
||||
.email("").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -239,7 +239,8 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "invalidemail", "invalid");
|
||||
updateProfilePage.prepareUpdate().username("invalid").firstName("New first").lastName("New last")
|
||||
.email("invalidemail").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -261,7 +262,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "");
|
||||
updateProfilePage.prepareUpdate().username("").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -284,7 +285,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -307,7 +308,8 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last")
|
||||
.email("keycloak-user@localhost").submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
|
@ -331,7 +333,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
// Expire cookies and assert the page with "back to application" link present
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("keycloak-user@localhost").submit();
|
||||
errorPage.assertCurrent();
|
||||
|
||||
String backToAppLink = errorPage.getBackToApplicationLink();
|
||||
|
@ -357,7 +359,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
updateProfilePage.assertCurrent();
|
||||
assertFalse(updateProfilePage.isCancelDisplayed());
|
||||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||
updateProfilePage.prepareUpdate().username("test-user@localhost").firstName("New first").lastName("New last").email("new@email.com").submit();
|
||||
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name()).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
|
||||
|
|
|
@ -284,7 +284,7 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
assertFalse(updateProfilePage.isCancelDisplayed());
|
||||
|
||||
updateProfilePage.update("New first", "", "new@email.com", USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("New first").lastName("").email("new@email.com").submit();
|
||||
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||
.detail(Details.PREVIOUS_LAST_NAME, "Brady")
|
||||
|
@ -319,11 +319,11 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
|
||||
updateProfilePage.assertCurrent();
|
||||
//submit with error
|
||||
updateProfilePage.update("First", "L", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("First").lastName("L").email(USERNAME1).submit();
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
//submit OK
|
||||
updateProfilePage.update("First", "Last", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("First").lastName("Last").email(USERNAME1).submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -352,7 +352,7 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
Assert.assertFalse(updateProfilePage.isDepartmentEnabled());
|
||||
|
||||
//update of the other attributes must be successful in this case
|
||||
updateProfilePage.update("First", "Last", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("First").lastName("Last").email(USERNAME1).submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -379,7 +379,7 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
Assert.assertFalse(updateProfilePage.isDepartmentEnabled());
|
||||
|
||||
//update of the other attributes must be successful in this case
|
||||
updateProfilePage.update("First", "Last", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("First").lastName("Last").email(USERNAME1).submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -406,7 +406,7 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
Assert.assertFalse("'department' field is visible" , updateProfilePage.isDepartmentPresent());
|
||||
|
||||
//update of the other attributes must be successful in this case
|
||||
updateProfilePage.update("First", "Last", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("First").lastName("Last").email(USERNAME1).submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -431,11 +431,13 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit with error
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("").submit();
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit OK
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("DepartmentCC").submit();
|
||||
|
||||
// we also test additional attribute configured to be audited in the event
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE)
|
||||
|
@ -470,11 +472,13 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit with error
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("").submit();
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit OK
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("DepartmentCC").submit();
|
||||
|
||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).client(client_scope_optional.getClientId())
|
||||
.detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "FirstCC")
|
||||
|
@ -508,11 +512,13 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit with error
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("").submit();
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit OK
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("DepartmentCC").submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -540,11 +546,13 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit with error
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("").submit();
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
//submit OK
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("DepartmentCC").submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -572,7 +580,8 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
Assert.assertTrue(updateProfilePage.isDepartmentPresent());
|
||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1)
|
||||
.department("DepartmentCC").submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
@ -600,7 +609,7 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
Assert.assertFalse(updateProfilePage.isDepartmentPresent());
|
||||
updateProfilePage.update("FirstCC", "LastCC", USERNAME1, USERNAME1);
|
||||
updateProfilePage.prepareUpdate().username(USERNAME1).firstName("FirstCC").lastName("LastCC").email(USERNAME1).submit();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
|
||||
package org.keycloak.testsuite.cluster;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.mail.MessagingException;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
|
@ -27,12 +32,6 @@ import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
|||
import org.openqa.selenium.Cookie;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
@ -109,7 +108,7 @@ public class AuthenticationSessionFailoverClusterTest extends AbstractFailoverCl
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
// Successfully update profile and assert user logged
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3").email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attributes;
|
||||
|
@ -35,7 +34,6 @@ import javax.naming.directory.DirContext;
|
|||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.security.sasl.Sasl;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.Credentials;
|
||||
|
@ -75,7 +73,6 @@ import org.keycloak.testsuite.AssertEvents;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.KerberosRule;
|
||||
|
|
|
@ -19,12 +19,9 @@ package org.keycloak.testsuite.federation.kerberos;
|
|||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
@ -36,11 +33,9 @@ import org.keycloak.representations.idm.ComponentRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.KerberosEmbeddedServer;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||
import org.keycloak.testsuite.util.KerberosRule;
|
||||
import org.keycloak.testsuite.KerberosEmbeddedServer;
|
||||
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
|
||||
/**
|
||||
* Test for the KerberosFederationProvider (kerberos without LDAP integration)
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import java.io.IOException;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -33,6 +33,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
|||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.InfoPage;
|
||||
|
@ -48,9 +49,6 @@ import org.keycloak.testsuite.util.GreenMailRule;
|
|||
import org.keycloak.testsuite.util.MailUtils;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
|
||||
/**
|
||||
* Test for browser back/forward/refresh buttons
|
||||
*
|
||||
|
@ -167,7 +165,7 @@ public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
|
||||
// Successfully update profile and assert user logged
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3").email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
@ -214,7 +212,7 @@ public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
// Successfully update profile and assert user logged
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3").email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
@ -228,7 +226,7 @@ public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
|
|||
loginPage.open();
|
||||
loginPage.login("login-test", "password");
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3").email("john@doe3.com").submit();
|
||||
|
||||
// Assert on consent screen
|
||||
grantPage.assertCurrent();
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
|
||||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -48,10 +52,6 @@ import org.keycloak.testsuite.util.GreenMailRule;
|
|||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
|
||||
|
||||
/**
|
||||
* Tries to simulate testing with multiple browser tabs
|
||||
*
|
||||
|
@ -145,7 +145,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
updatePasswordPage.assertCurrent();
|
||||
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
|
||||
// Try to go back to tab 1. We should have ALREADY_LOGGED_IN info page
|
||||
|
@ -184,7 +185,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Login success now
|
||||
loginPage.login("login-test", "password");
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
@ -208,7 +210,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Login success now
|
||||
loginPage.login("login-test", "password");
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
@ -233,7 +236,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
updatePasswordPage.assertCurrent();
|
||||
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
@ -271,7 +275,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
updatePasswordPage.assertCurrent();
|
||||
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
}
|
||||
|
||||
|
@ -297,7 +302,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
driver.navigate().to(tab1Url);
|
||||
loginPage.login("login-test", "password");
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
|
||||
// Assert I am redirected to the appPage in tab1
|
||||
appPage.assertCurrent();
|
||||
|
@ -332,7 +338,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
driver.navigate().to(tab1Url);
|
||||
loginPage.login("login-test", "password");
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
|
||||
// Assert I am redirected to the appPage in tab1 and have state corresponding to tab1
|
||||
appPage.assertCurrent();
|
||||
|
@ -365,7 +372,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Continue in tab2 and finish login here
|
||||
loginPage.login("login-test", "password");
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
|
||||
// Assert I am redirected to the appPage in tab2 and have state corresponding to tab2
|
||||
appPage.assertCurrent();
|
||||
|
@ -407,7 +415,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
updatePasswordPage.assertCurrent();
|
||||
|
||||
updatePasswordPage.changePassword("password", "password");
|
||||
updateProfilePage.update("John", "Doe3", "john@doe3.com");
|
||||
updateProfilePage.prepareUpdate().firstName("John").lastName("Doe3")
|
||||
.email("john@doe3.com").submit();
|
||||
appPage.assertCurrent();
|
||||
|
||||
// Try to go back to tab 1. We should have ALREADY_LOGGED_IN info page
|
||||
|
|
|
@ -17,14 +17,8 @@
|
|||
|
||||
package org.keycloak.testsuite.ui.account2.page;
|
||||
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.UIAssert.assertElementDisabled;
|
||||
import static org.keycloak.testsuite.util.UIAssert.assertInputElementValid;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
@ -32,7 +26,11 @@ import static org.keycloak.testsuite.util.UIUtils.getTextInputValue;
|
|||
import static org.keycloak.testsuite.util.UIUtils.isElementVisible;
|
||||
import static org.keycloak.testsuite.util.UIUtils.setTextInputValue;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
|
@ -54,6 +52,8 @@ public class PersonalInfoPage extends AbstractLoggedInPage {
|
|||
private WebElement cancelBtn;
|
||||
@FindBy(id = "delete-account")
|
||||
private WebElement deleteAccountSection;
|
||||
@FindBy(id = "update-email-btn")
|
||||
private WebElement updateEmailLink;
|
||||
|
||||
@Override
|
||||
public String getPageId() {
|
||||
|
@ -88,6 +88,18 @@ public class PersonalInfoPage extends AbstractLoggedInPage {
|
|||
assertInputElementValid(expected, email);
|
||||
}
|
||||
|
||||
public void assertUpdateEmailLinkVisible(boolean expected){
|
||||
if (updateEmailLink == null) {
|
||||
assertFalse(expected);
|
||||
return;
|
||||
}
|
||||
assertEquals(expected, isElementVisible(updateEmailLink));
|
||||
}
|
||||
|
||||
public void clickUpdateEmailLink(){
|
||||
clickLink(updateEmailLink);
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return getTextInputValue(firstName);
|
||||
}
|
||||
|
@ -149,7 +161,6 @@ public class PersonalInfoPage extends AbstractLoggedInPage {
|
|||
|
||||
public void setValues(UserRepresentation user, boolean includeUsername) {
|
||||
if (includeUsername) {setUsername(user.getUsername());}
|
||||
setEmail(user.getEmail());
|
||||
setFirstName(user.getFirstName());
|
||||
setLastName(user.getLastName());
|
||||
}
|
||||
|
|
|
@ -17,12 +17,24 @@
|
|||
|
||||
package org.keycloak.testsuite.ui.account2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.representations.idm.*;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
|
@ -32,18 +44,6 @@ import org.keycloak.testsuite.ui.account2.page.PersonalInfoPage;
|
|||
import org.keycloak.testsuite.ui.account2.page.SigningInPage;
|
||||
import org.keycloak.testsuite.util.LDAPRule;
|
||||
import org.keycloak.testsuite.util.LDAPTestUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.ClassRule;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
|
||||
/**
|
||||
* @author Alfredo Moises Boullosa <aboullos@redhat.com>
|
||||
|
|
|
@ -17,6 +17,16 @@
|
|||
|
||||
package org.keycloak.testsuite.ui.account2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
|
||||
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -28,15 +38,6 @@ import org.keycloak.testsuite.ui.account2.page.AbstractLoggedInPage;
|
|||
import org.keycloak.testsuite.ui.account2.page.PersonalInfoPage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
|
||||
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.ui.account2;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdateEmailPage;
|
||||
import org.keycloak.testsuite.ui.account2.page.AbstractLoggedInPage;
|
||||
import org.keycloak.testsuite.ui.account2.page.PersonalInfoPage;
|
||||
|
||||
@EnableFeature(Profile.Feature.UPDATE_EMAIL)
|
||||
public class UpdateEmailTest extends BaseAccountPageTest {
|
||||
|
||||
@Page
|
||||
private PersonalInfoPage personalInfoPage;
|
||||
|
||||
@Page
|
||||
private UpdateEmailPage updateEmailPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Override
|
||||
protected AbstractLoggedInPage getAccountPage() {
|
||||
return personalInfoPage;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
enableUpdateEmailRequiredAction();
|
||||
}
|
||||
|
||||
@After
|
||||
public void clean() {
|
||||
disableUpdateEmailRequiredAction();
|
||||
disableRegistration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailLinkVisibleWithUpdateEmailActionEnabled() {
|
||||
refreshPageAndWaitForLoad();
|
||||
personalInfoPage.assertUpdateEmailLinkVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailLinkNotVisibleWithoutUpdateEmailActionEnabled() {
|
||||
disableUpdateEmailRequiredAction();
|
||||
refreshPageAndWaitForLoad();
|
||||
personalInfoPage.assertUpdateEmailLinkVisible(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailLinkVisibleWithUpdateEmailActionEnabledAndRegistrationEmailAsUsernameAndEditUsernameNotAllowed() {
|
||||
enableRegistration(true, false);
|
||||
refreshPageAndWaitForLoad();
|
||||
personalInfoPage.assertUpdateEmailLinkVisible(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateUserInfoWithRegistrationEnabled() {
|
||||
enableRegistration(false, true);
|
||||
refreshPageAndWaitForLoad();
|
||||
|
||||
assertTrue(personalInfoPage.valuesEqual(testUser));
|
||||
personalInfoPage.assertSaveDisabled(false);
|
||||
|
||||
UserRepresentation newInfo = new UserRepresentation();
|
||||
newInfo.setUsername(testUser.getUsername());
|
||||
newInfo.setEmail(testUser.getEmail());
|
||||
newInfo.setFirstName("New First");
|
||||
newInfo.setLastName("New Last");
|
||||
|
||||
personalInfoPage.setValues(newInfo, true);
|
||||
assertTrue(personalInfoPage.valuesEqual(newInfo));
|
||||
personalInfoPage.assertSaveDisabled(false);
|
||||
personalInfoPage.clickSave();
|
||||
personalInfoPage.alert().assertSuccess();
|
||||
personalInfoPage.assertSaveDisabled(false);
|
||||
|
||||
personalInfoPage.navigateTo();
|
||||
personalInfoPage.valuesEqual(newInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aiaCancellationSucceeds() {
|
||||
refreshPageAndWaitForLoad();
|
||||
personalInfoPage.assertUpdateEmailLinkVisible(true);
|
||||
personalInfoPage.clickUpdateEmailLink();
|
||||
Assert.assertTrue(updateEmailPage.isCurrent());
|
||||
updateEmailPage.clickCancelAIA();
|
||||
Assert.assertTrue(personalInfoPage.isCurrent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmailSucceeds() {
|
||||
personalInfoPage.navigateTo();
|
||||
personalInfoPage.assertUpdateEmailLinkVisible(true);
|
||||
personalInfoPage.clickUpdateEmailLink();
|
||||
Assert.assertTrue(updateEmailPage.isCurrent());
|
||||
updateEmailPage.changeEmail("new-email@example.org");
|
||||
events.expectAccount(EventType.UPDATE_EMAIL).detail(Details.UPDATED_EMAIL, "new-email@example.org");
|
||||
Assert.assertEquals("new-email@example.org", testRealmResource().users().get(testUser.getId()).toRepresentation().getEmail());
|
||||
}
|
||||
|
||||
private void disableUpdateEmailRequiredAction() {
|
||||
RequiredActionProviderRepresentation updateEmail = testRealmResource().flows().getRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||
updateEmail.setEnabled(false);
|
||||
testRealmResource().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL.name(), updateEmail);
|
||||
}
|
||||
|
||||
private void enableUpdateEmailRequiredAction() {
|
||||
RequiredActionProviderRepresentation updateEmail = testRealmResource().flows().getRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||
updateEmail.setEnabled(true);
|
||||
testRealmResource().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_EMAIL.name(), updateEmail);
|
||||
}
|
||||
|
||||
private void enableRegistration(boolean emailAsUsername, boolean usernameEditionAllowed) {
|
||||
RealmRepresentation realmRepresentation = testRealmResource().toRepresentation();
|
||||
realmRepresentation.setRegistrationAllowed(true);
|
||||
realmRepresentation.setRegistrationEmailAsUsername(emailAsUsername);
|
||||
realmRepresentation.setEditUsernameAllowed(usernameEditionAllowed);
|
||||
testRealmResource().update(realmRepresentation);
|
||||
}
|
||||
|
||||
private void disableRegistration() {
|
||||
RealmRepresentation realmRepresentation = testRealmResource().toRepresentation();
|
||||
realmRepresentation.setRegistrationAllowed(false);
|
||||
realmRepresentation.setRegistrationEmailAsUsername(false);
|
||||
realmRepresentation.setEditUsernameAllowed(false);
|
||||
testRealmResource().update(realmRepresentation);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
emailVerificationSubject=V\u00e9rification du courriel
|
||||
emailVerificationBody=Quelqu''un vient de cr\u00e9er un compte "{2}" avec votre courriel. Si vous \u00eates \u00e0 l''origine de cette requ\u00eate, veuillez cliquer sur le lien ci-dessous afin de v\u00e9rifier votre adresse de courriel\n\n{0}\n\nCe lien expire dans {3}.\n\nSinon, veuillez ignorer ce message.
|
||||
emailVerificationBodyHtml=<p>Quelqu''un vient de cr\u00e9er un compte "{2}" avec votre courriel. Si vous \u00eates \u00e0 l''origine de cette requ\u00eate, veuillez cliquer sur le lien ci-dessous afin de v\u00e9rifier votre adresse de courriel</p><p><a href="{0}">{0}</a></p><p>Ce lien expire dans {3}.</p><p>Sinon, veuillez ignorer ce message.</p>
|
||||
emailUpdateConfirmationSubject=V\u00e9rification du nouveau courriel
|
||||
emailUpdateConfirmationBody=Afin d''utiliser le courriel {1} dans votre compte {2}, cliquez sur le lien ci-dessous\n\n{0}\n\nCe lien expire dans {3}.\n\nSinon, veuillez ignorer ce message.
|
||||
emailUpdateConfirmationBodyHtml=<p>Afin d''utiliser le courriel {1} dans votre compte {2}, cliquez sur le lien ci-dessous</p><p><a href="{0}">{0}</a></p><p>Ce lien expirera dans {3}.</p><p>Sinon, veuillez ignorer ce message.</p>
|
||||
passwordResetSubject=R\u00e9initialiser le mot de passe
|
||||
passwordResetBody=Quelqu''un vient de demander une r\u00e9initialisation de mot de passe pour votre compte {2}. Si vous \u00eates \u00e0 l''origine de cette requ\u00eate, veuillez cliquer sur le lien ci-dessous pour le mettre \u00e0 jour.\n\n{0}\n\nCe lien expire dans {3}.\n\nSinon, veuillez ignorer ce message ; aucun changement ne sera effectu\u00e9 sur votre compte.
|
||||
passwordResetBodyHtml=<p>Quelqu''un vient de demander une r\u00e9initialisation de mot de passe pour votre compte {2}. Si vous \u00eates \u00e0 l''origine de cette requ\u00eate, veuillez cliquer sur le lien ci-dessous pour le mettre \u00e0 jour.</p><p><a href="{0}">Lien pour r\u00e9initialiser votre mot de passe</a></p><p>Ce lien expire dans {3}.</p><p>Sinon, veuillez ignorer ce message ; aucun changement ne sera effectu\u00e9 sur votre compte.</p>
|
||||
|
|
|
@ -421,3 +421,5 @@ frontchannel-logout.message=Odhlašujete se z následujících aplikací
|
|||
logoutConfirmTitle=Odhlašování
|
||||
logoutConfirmHeader=Chcete se odhlásit?
|
||||
doLogout=Odhlásit
|
||||
|
||||
readOnlyUsernameMessage=Nemůžete aktualizovat své uživatelské jméno, protože je pouze pro čtení.
|
||||
|
|
|
@ -359,3 +359,5 @@ webauthn-error-auth-verification=Resultatet fra log ind med sikkerhedsnøgle er
|
|||
webauthn-error-register-verification=Resultatet fra registrering med sikkerhedsnøglen er ugyldigt.
|
||||
webauthn-error-user-not-found=Ukendt bruger authenticated med sikkerhedsnøglen.
|
||||
identity-provider-redirector=Forbind med en anden Identitetsudbyder
|
||||
|
||||
readOnlyUsernameMessage=Du kan ikke opdatere dit brugernavn da det er read-only.
|
||||
|
|
|
@ -378,3 +378,5 @@ errasingData=L\u00F6schen aller Ihrer Daten
|
|||
loggingOutImmediately=Sofortige Abmeldung
|
||||
accountUnusable=Eine sp\u00E4tere Nutzung der Anwendung ist mit diesem Konto nicht mehr m\u00F6glich
|
||||
userDeletedSuccessfully=Nutzer erfolgreich gel\u00F6scht
|
||||
|
||||
readOnlyUsernameMessage=Sie k\u00F6nnen Ihren Benutzernamen nicht \u00E4ndern, da er schreibgesch\u00FCtzt ist.
|
||||
|
|
|
@ -439,3 +439,5 @@ accountUnusable=Tämän sovelluksen käyttö ei myöhemmin enää ole mahdollist
|
|||
userDeletedSuccessfully=Käyttäjä poistettu onnistuneesti
|
||||
|
||||
access-denied=Pääsy evätty
|
||||
|
||||
readOnlyUsernameMessage=Et voi päivittää käyttäjänimeäsi, koska se on "vain-luku"-tilassa.
|
||||
|
|
|
@ -34,6 +34,11 @@ errorTitle=Nous sommes d\u00e9sol\u00e9s...
|
|||
errorTitleHtml=Nous sommes <strong>d\u00e9sol\u00e9s</strong>...
|
||||
emailVerifyTitle=V\u00e9rification du courriel
|
||||
emailForgotTitle=Mot de passe oubli\u00e9 ?
|
||||
updateEmailTitle=Mise \u00e0 jour du courriel
|
||||
emailUpdateConfirmationSentTitle=Courriel de confirmation envoy\u00e9
|
||||
emailUpdateConfirmationSent=Un courriel de confirmation a \u00e9t\u00e9 envoy\u00e9 \u00e0 {0}. Vous devez suivre les instructions de ce dernier afin de compl\u00e9ter la mise \u00e0 jour.
|
||||
emailUpdatedTitle=Adresse de courriel mis \u00e0 jour
|
||||
emailUpdated=La mise \u00e0 jour de votre adresse de courriel vers {0} a \u00e9t\u00e9 compl\u00e9t\u00e9e avec succ\u00e8s.
|
||||
updatePasswordTitle=Mise \u00e0 jour du mot de passe
|
||||
codeSuccessTitle=Code succ\u00e8s
|
||||
codeErrorTitle=Code d''erreur \: {0}
|
||||
|
@ -184,6 +189,7 @@ confirmLinkIdpContinue=Souhaitez-vous lier {0} \u00e0 votre compte existant
|
|||
configureTotpMessage=Vous devez configurer l''authentification par mobile pour activer votre compte.
|
||||
updateProfileMessage=Vous devez mettre \u00e0 jour votre profil pour activer votre compte.
|
||||
updatePasswordMessage=Vous devez changer votre mot de passe pour activer votre compte.
|
||||
updateEmailMessage=Vous devez mettre \u00e0 votre addresse de courriel pour activer votre compte.
|
||||
resetPasswordMessage=Vous devez changer votre mot de passe.
|
||||
verifyEmailMessage=Vous devez v\u00e9rifier votre courriel pour activer votre compte.
|
||||
linkIdpMessage=Vous devez v\u00e9rifier votre courriel pour lier votre compte avec {0}.
|
||||
|
|
|
@ -352,3 +352,5 @@ webauthn-error-user-not-found=Ismeretlen felhasználót hitelesítettünk a bizt
|
|||
|
||||
identity-provider-redirector=Összekötés másik személyazonosság-kezelővel
|
||||
identity-provider-login-label=Egyéb bejelentkezési módok
|
||||
|
||||
readOnlyUsernameMessage=A felhasználó név nem módosítható.
|
||||
|
|
|
@ -350,3 +350,5 @@ webauthn-error-register-verification=Il risultato della registrazione della chia
|
|||
webauthn-error-user-not-found=Utente sconosciuto autenticato con la chiave di sicurezza.
|
||||
|
||||
identity-provider-redirector=Connettiti con un altro identity provider.
|
||||
|
||||
readOnlyUsernameMessage=Non puoi aggiornare il tuo nome utente poich\u00E9 \u00e8 in modalit\u00e0 sola lettura.
|
||||
|
|
|
@ -352,3 +352,5 @@ webauthn-error-register-verification=セキュリティーキーの登録結果
|
|||
webauthn-error-user-not-found=セキュリティーキーで認証された不明なユーザー。
|
||||
|
||||
identity-provider-redirector=別のアイデンティティー・プロバイダーと接続する
|
||||
|
||||
readOnlyUsernameMessage=読み取り専用のため、ユーザー名を更新することはできません。
|
||||
|
|
|
@ -314,3 +314,5 @@ console-verify-email=Musisz zweryfikować swój adres e-mail. Wiadomość e-mai
|
|||
console-email-code=Kod z e-mail\:
|
||||
console-accept-terms=Akceptujesz warunki? [t/n]\:
|
||||
console-accept=t
|
||||
|
||||
readOnlyUsernameMessage=Zmiana nazwy użytkownika nie jest możliwa, ponieważ edycja konta jest zablokowana.
|
||||
|
|
|
@ -378,3 +378,4 @@ loggingOutImmediately=Sair da aplica\u00e7\u00e3o imediatamente
|
|||
accountUnusable=Qualquer uso subsequente da aplica\u00e7\u00e3o n\u00e3o ser\u00e1 poss\u00edvel com esta conta
|
||||
userDeletedSuccessfully=Usu\u00e1rio exclu\u00eddo com sucesso
|
||||
|
||||
readOnlyUsernameMessage=Voc\u00ea^n\u00e3o pode atualizar o seu nome de usu\u00e1rio, uma vez que \u00e9 apenas de leitura.
|
||||
|
|
|
@ -261,3 +261,5 @@ noCertificate=[Bez certifikátu]
|
|||
|
||||
pageNotFound=Stránka nebola nájdená
|
||||
internalServerError=Vyskytla sa interná chyba servera
|
||||
|
||||
readOnlyUsernameMessage=Nemôžete aktualizovať svoje používateľské meno, pretože je iba na čítanie.
|
||||
|
|
|
@ -292,3 +292,5 @@ console-verify-email=E-posta adresinizi do\u011Frulaman\u0131z gerekiyor. Bir do
|
|||
console-email-code=E-posta Kodu:
|
||||
console-accept-terms=\u015Eartlar\u0131 kabul et? [e/h]:
|
||||
console-accept=e
|
||||
|
||||
readOnlyUsernameMessage=Yazma korumal\u0131 oldu\u011Fundan kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 de\u011Fi\u015Ftiremezsiniz.
|
||||
|
|
|
@ -40,3 +40,5 @@ thirdPartyApp=Tierce
|
|||
inUse=Utilis\u00e9(e)
|
||||
notInUse=Non utilis\u00e9(e)
|
||||
setUpNew=Configurer {0}
|
||||
|
||||
updateEmail=Modifier le courriel
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
${kcSanitize(msg("emailUpdateConfirmationBodyHtml",link, newEmail, realmName, linkExpirationFormatter(linkExpiration)))?no_esc}
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +1,9 @@
|
|||
emailVerificationSubject=Verify email
|
||||
emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {3}.\n\nIf you didn''t create this account, just ignore this message.
|
||||
emailVerificationBodyHtml=<p>Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address</p><p><a href="{0}">Link to e-mail address verification</a></p><p>This link will expire within {3}.</p><p>If you didn''t create this account, just ignore this message.</p>
|
||||
emailUpdateConfirmationSubject=Verify new email
|
||||
emailUpdateConfirmationBody=To update your {2} account with email address {1}, click the link below\n\n{0}\n\nThis link will expire within {3}.\n\nIf you don''t want to proceed with this modification, just ignore this message.
|
||||
emailUpdateConfirmationBodyHtml=<p>To update your {2} account with email address {1}, click the link below</p><p><a href="{0}">{0}</a></p><p>This link will expire within {3}.</p><p>If you don''t want to proceed with this modification, just ignore this message.</p>
|
||||
emailTestSubject=[KEYCLOAK] - SMTP test message
|
||||
emailTestBody=This is a test message
|
||||
emailTestBodyHtml=<p>This is a test message</p>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<#ftl output_format="plainText">
|
||||
${msg("emailUpdateConfirmationBody",link, newEmail, realmName, linkExpirationFormatter(linkExpiration))}
|
|
@ -23,6 +23,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<#if user.editEmailAllowed>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
|
@ -40,6 +41,7 @@
|
|||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
|
|
|
@ -44,6 +44,11 @@ errorTitle=We are sorry...
|
|||
errorTitleHtml=We are <strong>sorry</strong> ...
|
||||
emailVerifyTitle=Email verification
|
||||
emailForgotTitle=Forgot Your Password?
|
||||
updateEmailTitle=Update email
|
||||
emailUpdateConfirmationSentTitle=Confirmation email sent
|
||||
emailUpdateConfirmationSent=A confirmation email has been sent to {0}. You must follow the instructions of the former to complete the email update.
|
||||
emailUpdatedTitle=Email updated
|
||||
emailUpdated=The account email has been successfully updated to {0}.
|
||||
updatePasswordTitle=Update password
|
||||
codeSuccessTitle=Success code
|
||||
codeErrorTitle=Error code\: {0}
|
||||
|
@ -264,6 +269,7 @@ configureTotpMessage=You need to set up Mobile Authenticator to activate your ac
|
|||
configureBackupCodesMessage=You need to set up Backup Codes to activate your account.
|
||||
updateProfileMessage=You need to update your user profile to activate your account.
|
||||
updatePasswordMessage=You need to change your password to activate your account.
|
||||
updateEmailMessage=You need to update your email address to activate your account.
|
||||
resetPasswordMessage=You need to change your password.
|
||||
verifyEmailMessage=You need to verify your email address to activate your account.
|
||||
linkIdpMessage=You need to verify your email address to link your account with {0}.
|
||||
|
@ -487,3 +493,4 @@ logoutConfirmTitle=Logging out
|
|||
logoutConfirmHeader=Do you want to logout?
|
||||
doLogout=Logout
|
||||
|
||||
readOnlyUsernameMessage=You can''t update your username as it is read-only.
|
||||
|
|
42
themes/src/main/resources/theme/base/login/update-email.ftl
Normal file
42
themes/src/main/resources/theme/base/login/update-email.ftl
Normal file
|
@ -0,0 +1,42 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('email'); section>
|
||||
<#if section = "header">
|
||||
${msg("updateEmailTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-update-email-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="email" name="email" value="${(email.value!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('email')>
|
||||
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('email'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<#if isAppInitiatedAction??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -46,7 +46,9 @@
|
|||
isEventsEnabled : ${isEventsEnabled?c},
|
||||
isMyResourcesEnabled : ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c},
|
||||
isTotpConfigured : ${isTotpConfigured?c},
|
||||
deleteAccountAllowed : ${deleteAccountAllowed?c}
|
||||
deleteAccountAllowed : ${deleteAccountAllowed?c},
|
||||
updateEmailFeatureEnabled: ${updateEmailFeatureEnabled?c},
|
||||
updateEmailActionEnabled: ${updateEmailActionEnabled?c}
|
||||
}
|
||||
|
||||
var availableLocales = [];
|
||||
|
|
|
@ -162,3 +162,5 @@ error-user-attribute-required=Please specify ''{0}''.
|
|||
error-invalid-date=''{0}'' is invalid date.
|
||||
error-username-invalid-character=''{0}'' contains invalid character.
|
||||
error-person-name-invalid-character='{0}' contains invalid character.
|
||||
|
||||
updateEmail=Update email
|
||||
|
|
|
@ -19,6 +19,7 @@ import { ActionGroup,
|
|||
Form,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
InputGroup,
|
||||
Grid,
|
||||
GridItem,
|
||||
ExpandableSection,
|
||||
|
@ -40,6 +41,7 @@ import { LocaleSelector } from '../../widgets/LocaleSelectors';
|
|||
import { KeycloakContext } from '../../keycloak-service/KeycloakContext';
|
||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||
import { AIACommand } from '../../util/AIACommand';
|
||||
import {ExternalLinkSquareAltIcon} from "@patternfly/react-icons";
|
||||
|
||||
declare const features: Features;
|
||||
declare const locale: string;
|
||||
|
@ -69,6 +71,8 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
private isRegistrationEmailAsUsername: boolean = features.isRegistrationEmailAsUsername;
|
||||
private isEditUserNameAllowed: boolean = features.isEditUserNameAllowed;
|
||||
private isDeleteAccountAllowed: boolean = features.deleteAccountAllowed;
|
||||
private isUpdateEmailFeatureEnabled: boolean = features.updateEmailFeatureEnabled;
|
||||
private isUpdateEmailActionEnabled: boolean = features.updateEmailActionEnabled;
|
||||
private readonly DEFAULT_STATE: AccountPageState = {
|
||||
errors: {
|
||||
username: '',
|
||||
|
@ -155,6 +159,10 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
new AIACommand(keycloak, "delete_account").execute();
|
||||
}
|
||||
|
||||
private handleEmailUpdate = (keycloak: KeycloakService): void => {
|
||||
new AIACommand(keycloak, "UPDATE_EMAIL").execute();
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const fields: FormFields = this.state.formFields;
|
||||
return (
|
||||
|
@ -189,8 +197,8 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
)}
|
||||
</FormGroup>
|
||||
)}
|
||||
<FormGroup
|
||||
label={Msg.localize("email")}
|
||||
{!this.isUpdateEmailFeatureEnabled && <FormGroup
|
||||
label={Msg.localize('email')}
|
||||
fieldId="email-address"
|
||||
helperTextInvalid={this.state.errors.email}
|
||||
validated={
|
||||
|
@ -213,7 +221,35 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
: ValidatedOptions.default
|
||||
}
|
||||
></TextInput>
|
||||
</FormGroup>
|
||||
</FormGroup> }
|
||||
{this.isUpdateEmailFeatureEnabled && <FormGroup
|
||||
label={Msg.localize('email')}
|
||||
fieldId="email-address"
|
||||
>
|
||||
<InputGroup>
|
||||
<TextInput
|
||||
isDisabled
|
||||
type="email"
|
||||
id="email-address"
|
||||
name="email"
|
||||
value={fields.email}
|
||||
>
|
||||
</TextInput>
|
||||
{this.isUpdateEmailActionEnabled && (!this.isRegistrationEmailAsUsername || this.isEditUserNameAllowed) &&
|
||||
<KeycloakContext.Consumer>
|
||||
{ (keycloak: KeycloakService) => (
|
||||
<Button id="update-email-btn"
|
||||
variant="link"
|
||||
onClick={() => this.handleEmailUpdate(keycloak)}
|
||||
icon={<ExternalLinkSquareAltIcon/>}
|
||||
iconPosition="right">
|
||||
<Msg msgKey="updateEmail" />
|
||||
</Button>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
}
|
||||
</InputGroup>
|
||||
</FormGroup> }
|
||||
<FormGroup
|
||||
label={Msg.localize("firstName")}
|
||||
fieldId="first-name"
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
isMyResourcesEnabled: boolean;
|
||||
isTotpConfigured: boolean;
|
||||
deleteAccountAllowed: boolean;
|
||||
updateEmailFeatureEnabled: boolean;
|
||||
updateEmailActionEnabled: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -540,7 +540,8 @@ ul#kc-totp-supported-apps {
|
|||
|
||||
#kc-form-login div.form-group:last-of-type,
|
||||
#kc-register-form div.form-group:last-of-type,
|
||||
#kc-update-profile-form div.form-group:last-of-type {
|
||||
#kc-update-profile-form div.form-group:last-of-type,
|
||||
#kc-update-email-form div.form-group:last-of-type{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue