[KEYCLOAK-18591] - Support a dynamic IDP user review form
This commit is contained in:
parent
333f77a039
commit
6686482ba5
15 changed files with 567 additions and 41 deletions
|
@ -26,6 +26,6 @@ public enum LoginFormsPages {
|
|||
LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
|
||||
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, LOGIN_SELECT_AUTHENTICATOR, REGISTER, REGISTER_USER_PROFILE, INFO, ERROR, ERROR_WEBAUTHN, LOGIN_UPDATE_PROFILE,
|
||||
LOGIN_PAGE_EXPIRED, CODE, X509_CONFIRM, SAML_POST_FORM,
|
||||
LOGIN_OAUTH2_DEVICE_VERIFY_USER_CODE, UPDATE_USER_PROFILE;
|
||||
LOGIN_OAUTH2_DEVICE_VERIFY_USER_CODE, UPDATE_USER_PROFILE, IDP_REVIEW_USER_PROFILE;
|
||||
|
||||
}
|
||||
|
|
|
@ -91,9 +91,17 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
|
|||
updateProfileFirstLogin = authenticatorConfig.getConfig().get(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN);
|
||||
}
|
||||
|
||||
RealmModel realm = context.getRealm();
|
||||
return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin)
|
||||
|| (IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin) && !Validation.validateUserMandatoryFields(realm, userCtx));
|
||||
if(IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin)) {
|
||||
try {
|
||||
UserProfileProvider profileProvider = context.getSession().getProvider(UserProfileProvider.class);
|
||||
profileProvider.create(UserProfileContext.IDP_REVIEW, userCtx.getAttributes()).validate();
|
||||
return false;
|
||||
} catch (ValidationException pve) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.forms.login.freemarker.model.AuthenticationContextBean;
|
|||
import org.keycloak.forms.login.freemarker.model.ClientBean;
|
||||
import org.keycloak.forms.login.freemarker.model.CodeBean;
|
||||
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
||||
import org.keycloak.forms.login.freemarker.model.IdpReviewProfileBean;
|
||||
import org.keycloak.forms.login.freemarker.model.LoginBean;
|
||||
import org.keycloak.forms.login.freemarker.model.OAuthGrantBean;
|
||||
import org.keycloak.forms.login.freemarker.model.ProfileBean;
|
||||
|
@ -63,6 +64,7 @@ import org.keycloak.theme.beans.MessageBean;
|
|||
import org.keycloak.theme.beans.MessageFormatterMethod;
|
||||
import org.keycloak.theme.beans.MessageType;
|
||||
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
|
@ -261,6 +263,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
case UPDATE_USER_PROFILE:
|
||||
attributes.put("profile", new VerifyProfileBean(user, formData, session));
|
||||
break;
|
||||
case IDP_REVIEW_USER_PROFILE:
|
||||
UpdateProfileContext idpCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||
attributes.put("profile", new IdpReviewProfileBean(idpCtx, formData, session));
|
||||
break;
|
||||
}
|
||||
|
||||
return processTemplate(theme, Templates.getTemplate(page), locale);
|
||||
|
@ -557,8 +563,16 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
setMessage(MessageType.WARNING, Messages.UPDATE_PROFILE);
|
||||
}
|
||||
|
||||
if(isDynamicUserProfile()) {
|
||||
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||
if(userCtx != null && userCtx.getUserProfileContext() == UserProfileContext.IDP_REVIEW)
|
||||
return createResponse(LoginFormsPages.IDP_REVIEW_USER_PROFILE);
|
||||
else
|
||||
return createResponse(LoginFormsPages.UPDATE_USER_PROFILE);
|
||||
} else {
|
||||
return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response createIdpLinkConfirmLinkPage() {
|
||||
|
|
|
@ -75,6 +75,7 @@ public class Templates {
|
|||
case SAML_POST_FORM:
|
||||
return "saml-post-form.ftl";
|
||||
case UPDATE_USER_PROFILE:
|
||||
case IDP_REVIEW_USER_PROFILE:
|
||||
return "update-user-profile.ftl";
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2016 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.authentication.requiredactions.util.UpdateProfileContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.userprofile.UserProfile;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
|
||||
/**
|
||||
* @author Vlastimil Elias <velias@redhat.com>
|
||||
*/
|
||||
public class IdpReviewProfileBean extends AbstractUserProfileBean {
|
||||
|
||||
private UpdateProfileContext idpCtx;
|
||||
|
||||
public IdpReviewProfileBean(UpdateProfileContext idpCtx, MultivaluedMap<String, String> formData, KeycloakSession session) {
|
||||
super(formData);
|
||||
this.idpCtx = idpCtx;
|
||||
init(session, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserProfile createUserProfile(UserProfileProvider provider) {
|
||||
return provider.create(UserProfileContext.IDP_REVIEW, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAttributeDefaultValue(String name) {
|
||||
return idpCtx.getFirstAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContext() {
|
||||
return UserProfileContext.IDP_REVIEW.name();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package org.keycloak.services.validation;
|
||||
|
||||
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.userprofile.ValidationException;
|
||||
|
||||
|
@ -42,17 +40,6 @@ public class Validation {
|
|||
errors.add(new FormMessage(field, message, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if user object contains all mandatory fields.
|
||||
*
|
||||
* @param realm user is for
|
||||
* @param user to validate
|
||||
* @return true if user object contains all mandatory values, false if some mandatory value is missing
|
||||
*/
|
||||
public static boolean validateUserMandatoryFields(RealmModel realm, UpdateProfileContext user){
|
||||
return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is empty (null or lenght is 0)
|
||||
*
|
||||
|
|
|
@ -285,7 +285,7 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
new AttributeValidatorMetadata(DuplicateUsernameValidator.ID),
|
||||
new AttributeValidatorMetadata(UsernameMutationValidator.ID)).setAttributeDisplayName("${username}");
|
||||
|
||||
metadata.addAttribute(UserModel.EMAIL, -1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL)),
|
||||
metadata.addAttribute(UserModel.EMAIL, -1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, false)),
|
||||
new AttributeValidatorMetadata(EmailValidator.ID, ValidatorConfig.builder().config(EmailValidator.IGNORE_EMPTY_VALUE, true).build()),
|
||||
new AttributeValidatorMetadata(DuplicateEmailValidator.ID),
|
||||
new AttributeValidatorMetadata(EmailExistsAsUsernameValidator.ID)).setAttributeDisplayName("${email}");
|
||||
|
@ -306,9 +306,10 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
private UserProfileMetadata createBrokeringProfile(AttributeValidatorMetadata readOnlyValidator) {
|
||||
UserProfileMetadata metadata = new UserProfileMetadata(IDP_REVIEW);
|
||||
|
||||
metadata.addAttribute(UserModel.USERNAME, -2, new AttributeValidatorMetadata(BrokeringFederatedUsernameHasValueValidator.ID)).setAttributeDisplayName("${username}");
|
||||
metadata.addAttribute(UserModel.USERNAME, -2, AbstractUserProfileProvider::editUsernameCondition,
|
||||
AbstractUserProfileProvider::editUsernameCondition, new AttributeValidatorMetadata(BrokeringFederatedUsernameHasValueValidator.ID)).setAttributeDisplayName("${username}");
|
||||
|
||||
metadata.addAttribute(UserModel.EMAIL, -1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL)),
|
||||
metadata.addAttribute(UserModel.EMAIL, -1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, true)),
|
||||
new AttributeValidatorMetadata(EmailValidator.ID)).setAttributeDisplayName("${email}");
|
||||
|
||||
List<AttributeValidatorMetadata> readonlyValidators = new ArrayList<>();
|
||||
|
|
|
@ -134,8 +134,8 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
|
|||
if (!isEnabled(session)) {
|
||||
if(!context.equals(UserProfileContext.USER_API) && !context.equals(UserProfileContext.REGISTRATION_USER_CREATION)) {
|
||||
decoratedMetadata.addAttribute(UserModel.FIRST_NAME, 1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(
|
||||
Messages.MISSING_FIRST_NAME))).setAttributeDisplayName("${firstName}");
|
||||
decoratedMetadata.addAttribute(UserModel.LAST_NAME, 2, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME))).setAttributeDisplayName("${lastName}");
|
||||
Messages.MISSING_FIRST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${firstName}");
|
||||
decoratedMetadata.addAttribute(UserModel.LAST_NAME, 2, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${lastName}");
|
||||
return decoratedMetadata;
|
||||
}
|
||||
return decoratedMetadata;
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.validate.SimpleValidator;
|
|||
import org.keycloak.validate.ValidationContext;
|
||||
import org.keycloak.validate.ValidationError;
|
||||
import org.keycloak.validate.ValidatorConfig;
|
||||
import org.keycloak.validate.ValidatorConfig.ValidatorConfigBuilder;
|
||||
|
||||
/**
|
||||
* Validator to check that User Profile attribute value is not blank (null value is OK!). Expects List of Strings as
|
||||
|
@ -37,6 +38,8 @@ public class BlankAttributeValidator implements SimpleValidator {
|
|||
|
||||
public static final String CFG_ERROR_MESSAGE = "error-message";
|
||||
|
||||
public static final String CFG_FAIL_ON_NULL = "fail-on-null";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
|
@ -47,13 +50,15 @@ public class BlankAttributeValidator implements SimpleValidator {
|
|||
@SuppressWarnings("unchecked")
|
||||
List<String> values = (List<String>) input;
|
||||
|
||||
if (values.isEmpty()) {
|
||||
boolean failOnNull = config.getBooleanOrDefault(CFG_FAIL_ON_NULL, false);
|
||||
|
||||
if (values.isEmpty() && !failOnNull) {
|
||||
return context;
|
||||
}
|
||||
|
||||
String value = values.get(0);
|
||||
String value = values.isEmpty() ? null: values.get(0);
|
||||
|
||||
if (value != null && Validation.isBlank(value)) {
|
||||
if ((failOnNull || value != null) && Validation.isBlank(value)) {
|
||||
context.addError(new ValidationError(ID, inputHint, config.getStringOrDefault(CFG_ERROR_MESSAGE, AttributeRequiredByMetadataValidator.ERROR_USER_ATTRIBUTE_REQUIRED)));
|
||||
}
|
||||
|
||||
|
@ -64,13 +69,16 @@ public class BlankAttributeValidator implements SimpleValidator {
|
|||
* Create config for this validator to get customized error message
|
||||
*
|
||||
* @param errorMessage to be used if validation fails
|
||||
* @param failOnNull makes validator fail on null values also (not on empty string only as is the default behavior)
|
||||
* @return config
|
||||
*/
|
||||
public static ValidatorConfig createConfig(String errorMessage) {
|
||||
public static ValidatorConfig createConfig(String errorMessage, boolean failOnNull) {
|
||||
ValidatorConfigBuilder builder = ValidatorConfig.builder();
|
||||
builder.config(CFG_FAIL_ON_NULL, failOnNull);
|
||||
if (errorMessage != null) {
|
||||
return ValidatorConfig.builder().config(CFG_ERROR_MESSAGE, errorMessage).build();
|
||||
builder.config(CFG_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
return ValidatorConfig.EMPTY;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.testsuite.pages;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
|
@ -19,6 +21,9 @@ public class UpdateAccountInformationPage extends LanguageComboboxAwarePage {
|
|||
@FindBy(id = "lastName")
|
||||
private WebElement lastNameInput;
|
||||
|
||||
@FindBy(id = "department")
|
||||
private WebElement departmentInput;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
|
@ -41,6 +46,29 @@ public class UpdateAccountInformationPage extends LanguageComboboxAwarePage {
|
|||
clickLink(submitButton);
|
||||
}
|
||||
|
||||
public void updateAccountInformation(String userName,
|
||||
String email,
|
||||
String firstName,
|
||||
String lastName,
|
||||
String department) {
|
||||
usernameInput.clear();
|
||||
usernameInput.sendKeys(userName);
|
||||
|
||||
emailInput.clear();
|
||||
emailInput.sendKeys(email);
|
||||
|
||||
firstNameInput.clear();
|
||||
firstNameInput.sendKeys(firstName);
|
||||
|
||||
lastNameInput.clear();
|
||||
lastNameInput.sendKeys(lastName);
|
||||
|
||||
departmentInput.clear();
|
||||
departmentInput.sendKeys(department);
|
||||
|
||||
clickLink(submitButton);
|
||||
}
|
||||
|
||||
public void updateAccountInformation(String email,
|
||||
String firstName,
|
||||
String lastName) {
|
||||
|
@ -72,6 +100,18 @@ public class UpdateAccountInformationPage extends LanguageComboboxAwarePage {
|
|||
return PageUtils.getPageTitle(driver).equalsIgnoreCase("update account information");
|
||||
}
|
||||
|
||||
public String getLabelForField(String fieldId) {
|
||||
return driver.findElement(By.cssSelector("label[for="+fieldId+"]")).getText();
|
||||
}
|
||||
|
||||
public boolean isDepartmentPresent() {
|
||||
try {
|
||||
return driver.findElement(By.id("department")).isDisplayed();
|
||||
} catch (NoSuchElementException nse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() throws Exception {
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.forms.VerifyProfileTest;
|
||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||
import org.keycloak.testsuite.util.MailServer;
|
||||
import org.keycloak.testsuite.util.MailServerConfiguration;
|
||||
|
@ -55,6 +56,17 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
@SecondBrowser
|
||||
protected WebDriver driver2;
|
||||
|
||||
protected void enableDynamicUserProfile() {
|
||||
|
||||
RealmResource rr = adminClient.realm(bc.consumerRealmName());
|
||||
|
||||
RealmRepresentation testRealm = rr.toRepresentation();
|
||||
|
||||
VerifyProfileTest.enableDynamicUserProfile(testRealm);
|
||||
|
||||
rr.update(testRealm);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refers to in old test suite: org.keycloak.testsuite.broker.AbstractFirstBrokerLoginTest#testErrorPageWhenDuplicationNotAllowed_updateProfileOn
|
||||
|
@ -452,18 +464,6 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFirstBrokerLoginFlowUpdateProfileOff() {
|
||||
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refers to in old test suite: org.keycloak.testsuite.broker.AbstractFirstBrokerLoginTest#testErrorPageWhenDuplicationNotAllowed_updateProfileOff
|
||||
*/
|
||||
|
@ -572,6 +572,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
updateAccountInformationPage.updateAccountInformation("test", "test@localhost.com", "FirstName", "LastName");
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals("test@localhost.com", accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals("test", accountUpdateProfilePage.getUsername());
|
||||
}
|
||||
|
||||
|
||||
|
@ -991,6 +995,11 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals("no-first-name@localhost.com", accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
|
||||
|
||||
|
||||
logoutFromRealm(getProviderRoot(), bc.providerRealmName());
|
||||
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
|
||||
|
@ -1009,6 +1018,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals("no-last-name@localhost.com", accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals("no-last-name", accountUpdateProfilePage.getUsername());
|
||||
|
||||
logoutFromRealm(getProviderRoot(), bc.providerRealmName());
|
||||
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
|
||||
|
@ -1028,6 +1041,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals("no-email@localhost.com", accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals("no-email", accountUpdateProfilePage.getUsername());
|
||||
}
|
||||
|
||||
|
||||
|
@ -1050,6 +1067,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals("all-info-set@localhost.com", accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals("all-info-set", accountUpdateProfilePage.getUsername());
|
||||
}
|
||||
|
||||
|
||||
|
@ -1064,6 +1085,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
|||
logInWithBroker(bc);
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals(bc.getUserEmail(), accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals(bc.getUserLogin(), accountUpdateProfilePage.getUsername());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -267,5 +267,14 @@ public class KcOidcFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
|
|||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
assertEquals("Please specify username.", loginUpdateProfilePage.getInputErrors().getUsernameError());
|
||||
|
||||
updateAccountInformationPage.updateAccountInformation("new-username", "no-first-name@localhost.com", "First Name", "Last Name");
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
Assert.assertEquals("First Name", accountUpdateProfilePage.getFirstName());
|
||||
Assert.assertEquals("Last Name", accountUpdateProfilePage.getLastName());
|
||||
Assert.assertEquals("no-first-name@localhost.com", accountUpdateProfilePage.getEmail());
|
||||
Assert.assertEquals("new-username", accountUpdateProfilePage.getUsername());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* 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.broker;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.ATTRIBUTE_DEPARTMENT;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.forms.VerifyProfileTest;
|
||||
import org.keycloak.testsuite.util.ClientScopeBuilder;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Vlastimil Elias <velias@redhat.com>
|
||||
*
|
||||
*/
|
||||
public class KcOidcFirstBrokerLoginWithUserProfileTest extends KcOidcFirstBrokerLoginTest {
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void beforeBrokerTest() {
|
||||
super.beforeBrokerTest();
|
||||
enableDynamicUserProfile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayName() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\", \"displayName\" : \"Department\", " + PERMISSIONS_ALL + ", \"required\":{}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
//assert field names
|
||||
// i18n replaced
|
||||
Assert.assertEquals("First name", updateAccountInformationPage.getLabelForField("firstName"));
|
||||
// attribute name used if no display name set
|
||||
Assert.assertEquals("lastName", updateAccountInformationPage.getLabelForField("lastName"));
|
||||
// direct value in display name
|
||||
Assert.assertEquals("Department", updateAccountInformationPage.getLabelForField("department"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttributeGuiOrder() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}},"
|
||||
+ "{\"name\": \"username\", " + VerifyProfileTest.PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"email\", " + VerifyProfileTest.PERMISSIONS_ALL + "}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
//assert fields location in form
|
||||
String htmlFormId = "kc-update-profile-form";
|
||||
Assert.assertTrue(
|
||||
driver.findElement(
|
||||
By.cssSelector("form#"+htmlFormId+" > div:nth-child(1) > div:nth-child(2) > input#lastName")
|
||||
).isDisplayed()
|
||||
);
|
||||
Assert.assertTrue(
|
||||
driver.findElement(
|
||||
By.cssSelector("form#"+htmlFormId+" > div:nth-child(2) > div:nth-child(2) > input#department")
|
||||
).isDisplayed()
|
||||
);
|
||||
Assert.assertTrue(
|
||||
driver.findElement(
|
||||
By.cssSelector("form#"+htmlFormId+" > div:nth-child(3) > div:nth-child(2) > input#username")
|
||||
).isDisplayed()
|
||||
);
|
||||
Assert.assertTrue(
|
||||
driver.findElement(
|
||||
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(2) > input#firstName")
|
||||
).isDisplayed()
|
||||
);
|
||||
Assert.assertTrue(
|
||||
driver.findElement(
|
||||
By.cssSelector("form#"+htmlFormId+" > div:nth-child(5) > div:nth-child(2) > input#email")
|
||||
).isDisplayed()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReviewWhenMissing_requiredReadOnlyAttributeDoesnotForceUpdate() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\", " + PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReviewWhenMissing_requiredButNotSelectedByScopeAttributeDoesnotForceUpdate() {
|
||||
|
||||
addDepartmentScopeIntoRealm();
|
||||
|
||||
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\", " + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"department\"]}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReviewWhenMissing_requiredAndSelectedByScopeAttributeForcesUpdate() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||
|
||||
//we use 'profile' scope which is requested by default
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\", " + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"profile\"]}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReview_requiredReadOnlyAttributeNotRenderedAndNotBlockingProcess() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\", " + PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
Assert.assertFalse(updateAccountInformationPage.isDepartmentPresent());
|
||||
|
||||
|
||||
updateAccountInformationPage.updateAccountInformation( "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration@email", "FirstAA", "LastAA");
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReview_attributeRequiredAndSelectedByScopeMustBeSet() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
//we use 'profile' scope which is requested by default
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + "},"
|
||||
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"profile\"]}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
//check required validation works
|
||||
updateAccountInformationPage.updateAccountInformation( "attributeRequiredAndSelectedByScopeMustBeSet", "attributeRequiredAndSelectedByScopeMustBeSet@email", "FirstAA", "LastAA", "");
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
updateAccountInformationPage.updateAccountInformation( "attributeRequiredAndSelectedByScopeMustBeSet", "attributeRequiredAndSelectedByScopeMustBeSet@email", "FirstAA", "LastAA", "DepartmentAA");
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
|
||||
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeRequiredAndSelectedByScopeMustBeSet");
|
||||
assertEquals("FirstAA", user.getFirstName());
|
||||
assertEquals("LastAA", user.getLastName());
|
||||
assertEquals("DepartmentAA", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReview_attributeNotRequiredAndSelectedByScopeCanBeIgnored() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
//we use 'profile' scope which is requested by default
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\"profile\"]}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
Assert.assertTrue(updateAccountInformationPage.isDepartmentPresent());
|
||||
updateAccountInformationPage.updateAccountInformation( "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "FirstAA", "LastAA");
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
|
||||
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeNotRequiredAndSelectedByScopeCanBeIgnored");
|
||||
assertEquals("FirstAA", user.getFirstName());
|
||||
assertEquals("LastAA", user.getLastName());
|
||||
assertEquals("", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReview_attributeNotRequiredAndSelectedByScopeCanBeSet() {
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
//we use 'profile' scope which is requested by default
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\"profile\"]}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
Assert.assertTrue(updateAccountInformationPage.isDepartmentPresent());
|
||||
updateAccountInformationPage.updateAccountInformation( "attributeNotRequiredAndSelectedByScopeCanBeSet", "attributeNotRequiredAndSelectedByScopeCanBeSet@email", "FirstAA", "LastAA","Department AA");
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
|
||||
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeNotRequiredAndSelectedByScopeCanBeSet");
|
||||
assertEquals("FirstAA", user.getFirstName());
|
||||
assertEquals("LastAA", user.getLastName());
|
||||
assertEquals("Department AA", user.firstAttribute(ATTRIBUTE_DEPARTMENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicUserProfileReview_attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingProcess() {
|
||||
|
||||
addDepartmentScopeIntoRealm();
|
||||
|
||||
updateExecutions(AbstractBrokerTest::enableUpdateProfileOnFirstLogin);
|
||||
|
||||
setUserProfileConfiguration("{\"attributes\": ["
|
||||
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||
+ "{\"name\": \"department\"," + PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\"department\"]}}"
|
||||
+ "]}");
|
||||
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "update account information", false);
|
||||
updateAccountInformationPage.assertCurrent();
|
||||
|
||||
Assert.assertFalse(updateAccountInformationPage.isDepartmentPresent());
|
||||
updateAccountInformationPage.updateAccountInformation( "attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration", "attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration@email", "FirstAA", "LastAA");
|
||||
|
||||
waitForAccountManagementTitle();
|
||||
accountUpdateProfilePage.assertCurrent();
|
||||
|
||||
UserRepresentation user = VerifyProfileTest.getUserByUsername(testRealm(),"attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration");
|
||||
assertEquals("FirstAA", user.getFirstName());
|
||||
assertEquals("LastAA", user.getLastName());
|
||||
assertEquals(null, user.firstAttribute(ATTRIBUTE_DEPARTMENT));
|
||||
}
|
||||
|
||||
public void addDepartmentScopeIntoRealm() {
|
||||
testRealm().clientScopes().create(ClientScopeBuilder.create().name("department").protocol("openid-connect").build());
|
||||
}
|
||||
|
||||
protected void setUserProfileConfiguration(String configuration) {
|
||||
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
|
||||
}
|
||||
|
||||
private RealmResource testRealm() {
|
||||
return adminClient.realm(bc.consumerRealmName());
|
||||
}
|
||||
|
||||
}
|
|
@ -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.testsuite.broker;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Vlastimil Elias <velias@redhat.com>
|
||||
*
|
||||
*/
|
||||
public class KcSamlFirstBrokerLoginWithUserProfileTest extends KcSamlFirstBrokerLoginTest {
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void beforeBrokerTest() {
|
||||
super.beforeBrokerTest();
|
||||
enableDynamicUserProfile();
|
||||
}
|
||||
|
||||
}
|
|
@ -1046,6 +1046,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
attributes.put(UserModel.USERNAME, "user");
|
||||
attributes.put(UserModel.EMAIL, "user@email.test");
|
||||
|
||||
// client with default scopes for which is attribute NOT configured as required
|
||||
configureAuthenticationSession(session, "client-b", null);
|
||||
|
|
Loading…
Reference in a new issue