Updating theme templates to render user attributes based on the user profile configuration
Closes #25149 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
d841971ff4
commit
778847a3ce
22 changed files with 649 additions and 384 deletions
|
@ -71,3 +71,20 @@ This strategy provides consistency in how attributes are managed by clients and
|
|||
configuration set to a realm.
|
||||
|
||||
For more details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
||||
|
||||
= Changes to Freemarker templates to allow rendering pages based on the user profile configuration set to a realm
|
||||
|
||||
In this release, the following templates were updated to make it possible to dynamically render attributes based
|
||||
on the user profile configuration set to a realm:
|
||||
|
||||
* `login-update-profile.ftl`
|
||||
* `register.ftl`
|
||||
|
||||
For more details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
||||
|
||||
= The update profile page when logging in for the first time through a broker now have its own Freemarker templates
|
||||
|
||||
In this release, the server will render the update profile page when the user is authenticating through a broker for the
|
||||
first time using the `idp-review-user-profile.ftl` template.
|
||||
|
||||
For more details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
|
@ -70,3 +70,40 @@ this method is not available from the representation payload and it is targeted
|
|||
= `https-client-auth` is a build time option
|
||||
|
||||
Option `https-client-auth` had been treated as a run time option, however this is not supported by Quarkus. The option needs to be handled at build time instead.
|
||||
|
||||
= Changes to Freemarker templates to allow rendering pages based on the user profile configuration set to a realm
|
||||
|
||||
In this release, the following templates were updated to make it possible to dynamically render attributes based
|
||||
on the user profile configuration set to a realm:
|
||||
|
||||
* `login-update-profile.ftl`
|
||||
* `register.ftl`
|
||||
|
||||
These templates are responsible for rendering both update profile (when the `Update Profile` required action is enabled to a user)
|
||||
and registration pages, respectively.
|
||||
|
||||
If you use a custom theme to change these templates, they will function as expect because only the content is updated.
|
||||
However, we recommend you to take a look at how to configure a link:{adminguide_link}#user-profile[{declarative user profile}] and possibly avoid
|
||||
changing the built-in templates by using all the capabilities provided by this feature.
|
||||
|
||||
Also, the templates used by the `declarative-user-profile` feature to render the pages for the same flows are longer necessary and removed in this release:
|
||||
|
||||
* `update-user-profile.ftl`
|
||||
* `register-user-profile.ftl`
|
||||
|
||||
If you were using the `declarative-user-profile` feature in previously releases with customizations to the above templates,
|
||||
update the `login-update-profile.ftl` and `register.ftl` accordingly.
|
||||
|
||||
= The update profile page when logging in for the first time through a broker now have its own Freemarker templates
|
||||
|
||||
In this release, the server will render the update profile page when the user is authenticating through a broker for the
|
||||
first time using the `idp-review-user-profile.ftl` template.
|
||||
|
||||
In previous releases, the template used to update the profile during the first broker login flow was the `login-update-profile.ftl`, the same used
|
||||
to update profile when users are authenticating to a realm.
|
||||
|
||||
By using separate templates for each flow, a more clear distinction exist as to which flow a template is actually used rather than sharing a same template,
|
||||
and potentially introduce unexpected changes and behavior that should only affect pages for a specific flow.
|
||||
|
||||
If you have customizations to the `login-update-profile.ftl` template to customize how users update their profiles when authenticating through a broker, make sure to move your changes
|
||||
to the new template.
|
|
@ -24,9 +24,9 @@ public enum LoginFormsPages {
|
|||
|
||||
LOGIN, LOGIN_USERNAME, LOGIN_PASSWORD, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_WEBAUTHN, LOGIN_VERIFY_EMAIL,
|
||||
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,
|
||||
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, LOGIN_SELECT_AUTHENTICATOR, REGISTER, 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, IDP_REVIEW_USER_PROFILE,
|
||||
LOGIN_OAUTH2_DEVICE_VERIFY_USER_CODE, IDP_REVIEW_USER_PROFILE,
|
||||
LOGIN_RECOVERY_AUTHN_CODES_INPUT, LOGIN_RECOVERY_AUTHN_CODES_CONFIG,
|
||||
FRONTCHANNEL_LOGOUT, LOGOUT_CONFIRM, UPDATE_EMAIL, LOGIN_RESET_OTP;
|
||||
|
||||
|
|
|
@ -337,22 +337,20 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
|||
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, ?> entry : attributes.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String name = entry.getKey();
|
||||
|
||||
if (!isSupportedAttribute(key)) {
|
||||
if (!isManagedAttribute(key) && isAllowUnmanagedAttribute()) {
|
||||
unmanagedAttributes.put(key, normalizeAttributeValues(key, entry.getValue()));
|
||||
if (!isSupportedAttribute(name)) {
|
||||
if (!isManagedAttribute(name) && isAllowUnmanagedAttribute()) {
|
||||
String normalizedName = normalizeAttributeName(name);
|
||||
unmanagedAttributes.put(normalizedName, normalizeAttributeValues(normalizedName, entry.getValue()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
|
||||
key = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
|
||||
}
|
||||
String normalizedName = normalizeAttributeName(name);
|
||||
List<String> values = normalizeAttributeValues(normalizedName, entry.getValue());
|
||||
|
||||
List<String> values = normalizeAttributeValues(key, entry.getValue());
|
||||
|
||||
newAttributes.put(key, Collections.unmodifiableList(values));
|
||||
newAttributes.put(normalizedName, Collections.unmodifiableList(values));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,6 +396,13 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
|||
return newAttributes;
|
||||
}
|
||||
|
||||
private static String normalizeAttributeName(String name) {
|
||||
if (name.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
|
||||
return name.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private List<String> normalizeAttributeValues(String name, Object value) {
|
||||
List<String> values;
|
||||
|
||||
|
@ -472,7 +477,7 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
|||
}
|
||||
|
||||
private boolean isManagedAttribute(String name) {
|
||||
return metadataByAttribute.containsKey(name);
|
||||
return metadataByAttribute.containsKey(normalizeAttributeName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -144,11 +144,31 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
|
|||
return userCtx.getFirstAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstName(String firstName) {
|
||||
userCtx.setFirstName(firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
userCtx.setEmail(email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
userCtx.setLastName(lastName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return userCtx.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
userCtx.setUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return null;
|
||||
|
|
|
@ -91,7 +91,6 @@ import org.keycloak.forms.login.MessageType;
|
|||
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
/**
|
||||
|
@ -161,13 +160,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
break;
|
||||
case UPDATE_PROFILE:
|
||||
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, new UserUpdateProfileContext(realm, user));
|
||||
|
||||
actionMessage = Messages.UPDATE_PROFILE;
|
||||
if(isDynamicUserProfile()) {
|
||||
page = LoginFormsPages.UPDATE_USER_PROFILE;
|
||||
} else {
|
||||
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||
}
|
||||
break;
|
||||
case UPDATE_EMAIL:
|
||||
actionMessage = Messages.UPDATE_EMAIL;
|
||||
|
@ -187,9 +181,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
case VERIFY_PROFILE:
|
||||
UpdateProfileContext verifyProfile = new UserUpdateProfileContext(realm, user);
|
||||
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, verifyProfile);
|
||||
|
||||
actionMessage = Messages.UPDATE_PROFILE;
|
||||
page = LoginFormsPages.UPDATE_USER_PROFILE;
|
||||
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||
break;
|
||||
default:
|
||||
return Response.serverError().build();
|
||||
|
@ -247,6 +240,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
attributes.put("recoveryAuthnCodesInputBean", new RecoveryAuthnCodeInputLoginBean(session, realm, user));
|
||||
break;
|
||||
case LOGIN_UPDATE_PROFILE:
|
||||
attributes.put("profile", new VerifyProfileBean(user, formData, session));
|
||||
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||
attributes.put("user", new ProfileBean(userCtx, formData));
|
||||
break;
|
||||
|
@ -274,9 +268,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
attributes.put("configuredOtpCredentials", new TotpLoginBean(session, realm, user, (String) this.attributes.get(OTPFormAuthenticator.SELECTED_OTP_CREDENTIAL_ID)));
|
||||
break;
|
||||
case REGISTER:
|
||||
if(isDynamicUserProfile()) {
|
||||
page = LoginFormsPages.REGISTER_USER_PROFILE;
|
||||
}
|
||||
RegisterBean rb = new RegisterBean(formData,session);
|
||||
//legacy bean for static template
|
||||
attributes.put("register", rb);
|
||||
|
@ -297,11 +288,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
case SAML_POST_FORM:
|
||||
attributes.put("samlPost", new SAMLPostFormBean(formData));
|
||||
break;
|
||||
case UPDATE_USER_PROFILE:
|
||||
attributes.put("profile", new VerifyProfileBean(user, formData, session));
|
||||
userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||
attributes.put("user", new ProfileBean(userCtx, formData));
|
||||
break;
|
||||
case IDP_REVIEW_USER_PROFILE:
|
||||
UpdateProfileContext idpCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||
attributes.put("profile", new IdpReviewProfileBean(idpCtx, formData, session));
|
||||
|
@ -317,10 +303,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return processTemplate(theme, Templates.getTemplate(page), locale);
|
||||
}
|
||||
|
||||
private boolean isDynamicUserProfile() {
|
||||
return session.getProvider(UserProfileProvider.class).isEnabled(realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sure that correct hostname and path is used for totp form.
|
||||
* Relevant when running in proxy mode.
|
||||
|
@ -646,15 +628,13 @@ 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)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -62,8 +62,6 @@ public class Templates {
|
|||
return "select-authenticator.ftl";
|
||||
case REGISTER:
|
||||
return "register.ftl";
|
||||
case REGISTER_USER_PROFILE:
|
||||
return "register-user-profile.ftl";
|
||||
case INFO:
|
||||
return "info.ftl";
|
||||
case ERROR:
|
||||
|
@ -82,8 +80,6 @@ public class Templates {
|
|||
return "login-x509-info.ftl";
|
||||
case SAML_POST_FORM:
|
||||
return "saml-post-form.ftl";
|
||||
case UPDATE_USER_PROFILE:
|
||||
return "update-user-profile.ftl";
|
||||
case IDP_REVIEW_USER_PROFILE:
|
||||
return "idp-review-user-profile.ftl";
|
||||
case FRONTCHANNEL_LOGOUT:
|
||||
|
|
|
@ -83,7 +83,7 @@ public class LegacyAttributes extends DefaultAttributes {
|
|||
@Override
|
||||
public Map<String, List<String>> getReadable() {
|
||||
if(user == null || user.getAttributes() == null) {
|
||||
return Collections.emptyMap();
|
||||
return super.getReadable();
|
||||
}
|
||||
|
||||
return new HashMap<>(user.getAttributes());
|
||||
|
|
|
@ -22,6 +22,7 @@ import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
|||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
|
@ -66,6 +67,10 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
|||
prepareUpdate().firstName(firstName).lastName(lastName).email(email).submit();
|
||||
}
|
||||
|
||||
public void update(Map<String, String> attributes) {
|
||||
prepareUpdate().otherProfileAttribute(attributes).submit();
|
||||
}
|
||||
|
||||
public Update prepareUpdate() {
|
||||
return new Update(this);
|
||||
}
|
||||
|
@ -176,8 +181,8 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Update otherProfileAttribute(String name, String value) {
|
||||
other.put(name, value);
|
||||
public Update otherProfileAttribute(Map<String, String> attributes) {
|
||||
other.putAll(attributes);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,12 @@
|
|||
|
||||
package org.keycloak.testsuite.pages;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.testsuite.auth.page.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.PasswordFields;
|
||||
|
@ -76,14 +80,18 @@ public class RegisterPage extends AbstractPage {
|
|||
private WebElement backToLoginLink;
|
||||
|
||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
|
||||
register(firstName, lastName, email, username, password, passwordConfirm, null);
|
||||
register(firstName, lastName, email, username, password, passwordConfirm, null, null, null);
|
||||
}
|
||||
|
||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm, String department) {
|
||||
register(firstName, lastName, email, username, password, passwordConfirm, department, null);
|
||||
register(firstName, lastName, email, username, password, passwordConfirm, department, null, null);
|
||||
}
|
||||
|
||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm, String department, Boolean termsAccepted) {
|
||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm, Map<String, String> attributes) {
|
||||
register(firstName, lastName, email, username, password, passwordConfirm, null, null, attributes);
|
||||
}
|
||||
|
||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm, String department, Boolean termsAccepted, Map<String, String> attributes) {
|
||||
firstNameInput.clear();
|
||||
if (firstName != null) {
|
||||
firstNameInput.sendKeys(firstName);
|
||||
|
@ -125,6 +133,12 @@ public class RegisterPage extends AbstractPage {
|
|||
termsAcceptedInput.click();
|
||||
}
|
||||
|
||||
if (attributes != null) {
|
||||
for (Entry<String, String> attribute : attributes.entrySet()) {
|
||||
driver.findElement(By.id(Constants.USER_ATTRIBUTES_PREFIX + attribute.getKey())).sendKeys(attribute.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
|
|
|
@ -55,10 +55,10 @@ public class KcOidcBrokerUiLocalesWithIdpHintTest extends AbstractBrokerTest {
|
|||
driver.getPageSource(), containsString("Jelentkezzen be a fiókjába")); // Sign in to your account
|
||||
|
||||
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
|
||||
waitForPage(driver, "felhasználói fiók adatok módosítása", false); // update account information
|
||||
waitForPage(driver, "fiók adatainak módosítása", false); // update account information
|
||||
|
||||
assertThat("The consumer realm should be in Hungarian even after the redirect from the IDP.",
|
||||
driver.getPageSource(), containsString("Felhasználói fiók adatok módosítása"));// update account information
|
||||
driver.getPageSource(), containsString("Fiók adatainak módosítása"));// update account information
|
||||
|
||||
assertThat("We must be on correct realm right now",
|
||||
driver.getCurrentUrl(), containsString("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||
|
|
|
@ -727,7 +727,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
|||
registerPage.assertCurrent();
|
||||
|
||||
registerPage.register("firstName", "lastName", "registerUserMissingTermsAcceptance@email",
|
||||
"registerUserMissingTermsAcceptance", "password", "password", null, false);
|
||||
"registerUserMissingTermsAcceptance", "password", "password", null, false, null);
|
||||
|
||||
registerPage.assertCurrent();
|
||||
assertEquals("You must agree to our terms and conditions.", registerPage.getInputAccountErrors().getTermsError());
|
||||
|
@ -753,7 +753,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
|||
registerPage.assertCurrent();
|
||||
|
||||
registerPage.register("firstName", "lastName", "registerUserSuccessTermsAcceptance@email",
|
||||
"registerUserSuccessTermsAcceptance", "password", "password", null, true);
|
||||
"registerUserSuccessTermsAcceptance", "password", "password", null, true, null);
|
||||
|
||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
|
|
|
@ -540,7 +540,7 @@ public class RegisterWithUserProfileTest extends RegisterTest {
|
|||
registerPage.assertCurrent();
|
||||
|
||||
Assert.assertTrue(registerPage.isDepartmentPresent());
|
||||
registerPage.register("FirstAA", "LastAA", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "password", "password", null);
|
||||
registerPage.register("FirstAA", "LastAA", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "password", "password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2023 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.theme;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.RegisterPage;
|
||||
|
||||
public class CustomRegistrationTemplateTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
static final Map<String, String> CUSTOM_ATTRIBUTES = Map.of("street", "street",
|
||||
"locality",
|
||||
"locality",
|
||||
"region", "region",
|
||||
"postal_code", "postal_code",
|
||||
"country", "country");
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
@Page
|
||||
protected RegisterPage registerPage;
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
testRealm.setRegistrationAllowed(true);
|
||||
testRealm.setLoginTheme("address");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegistration() {
|
||||
//contains few special characters we want to be sure they are allowed in username
|
||||
UserRepresentation user = register();
|
||||
Map<String, List<String>> attributes = user.getAttributes();
|
||||
assertFalse(attributes.isEmpty());
|
||||
assertCustomAttributes(attributes);
|
||||
}
|
||||
|
||||
protected static void assertCustomAttributes(Map<String, List<String>> attributes) {
|
||||
for (Entry<String, String> attribute : CUSTOM_ATTRIBUTES.entrySet()) {
|
||||
String name = attribute.getKey();
|
||||
List<String> values = attributes.get(name);
|
||||
assertNotNull(values);
|
||||
assertFalse(values.isEmpty());
|
||||
assertEquals(CUSTOM_ATTRIBUTES.get(name), values.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
protected UserRepresentation getUser(String username) {
|
||||
List<UserRepresentation> users = testRealm().users().search(username);
|
||||
assertFalse(users.isEmpty());
|
||||
return testRealm().users().get(users.get(0).getId()).toRepresentation();
|
||||
}
|
||||
|
||||
protected UserRepresentation register() {
|
||||
navigateToRegistrationPage();
|
||||
String username = "jdoe";
|
||||
registerPage.register("firstName", "lastName", username + "@keycloak.org", username, "password", "password", CUSTOM_ATTRIBUTES);
|
||||
UserRepresentation user = getUser(username);
|
||||
getCleanup().addUserId(user.getId());
|
||||
return user;
|
||||
}
|
||||
|
||||
private void navigateToRegistrationPage() {
|
||||
RealmRepresentation realm = testRealm().toRepresentation();
|
||||
realm.setRegistrationAllowed(true);
|
||||
testRealm().update(realm);
|
||||
loginPage.open();
|
||||
loginPage.clickRegister();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2023 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.theme;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.disableDynamicUserProfile;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
|
||||
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
|
||||
public class CustomRegistrationTemplateUserProfileTest extends CustomRegistrationTemplateTest {
|
||||
|
||||
private UPConfig upConfig;
|
||||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
upConfig = updateUserProfileConfiguration();
|
||||
}
|
||||
|
||||
@After
|
||||
public void onAfter() {
|
||||
disableDynamicUserProfile(testRealm());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void testRegistration() {
|
||||
upConfig.setUnmanagedAttributePolicy(null);
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
super.testRegistration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedAttributeEnabled() {
|
||||
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
|
||||
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
|
||||
upConfig.removeAttribute(name);
|
||||
}
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
UserRepresentation user = register();
|
||||
assertCustomAttributes(user.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedAttributeAdminEdit() {
|
||||
upConfig.setUnmanagedAttributePolicy(null);
|
||||
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
|
||||
upConfig.removeAttribute(name);
|
||||
}
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
UserRepresentation user = register();
|
||||
assertNull(user.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedAttributeDisabled() {
|
||||
upConfig.setUnmanagedAttributePolicy(null);
|
||||
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
|
||||
upConfig.removeAttribute(name);
|
||||
}
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
UserRepresentation user = register();
|
||||
assertNull(user.getAttributes());
|
||||
}
|
||||
|
||||
private UPConfig updateUserProfileConfiguration() {
|
||||
RealmRepresentation realm = testRealm().toRepresentation();
|
||||
enableDynamicUserProfile(realm);
|
||||
testRealm().update(realm);
|
||||
UPConfig upCOnfig = testRealm().users().userProfile().getConfiguration();
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("street", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("locality", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("region", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("postal_code", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("country", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
testRealm().users().userProfile().update(upCOnfig);
|
||||
return upCOnfig;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright 2023 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.theme;
|
||||
|
||||
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 java.util.AbstractMap;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.Constants;
|
||||
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.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
public class CustomUpdateProfileTemplateTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
static final Map<String, String> CUSTOM_ATTRIBUTES = Map.of("street", "street",
|
||||
"locality",
|
||||
"locality",
|
||||
"region", "region",
|
||||
"postal_code", "postal_code",
|
||||
"country", "country");
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected LoginUpdateProfilePage updateProfilePage;
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
testRealm.setLoginTheme("address");
|
||||
// the custom theme expects email as username and the username field is not rendered at all
|
||||
testRealm.setRegistrationEmailAsUsername(true);
|
||||
UserRepresentation user = UserBuilder.create().enabled(true)
|
||||
.username("tom")
|
||||
.email("tom@keycloak.org")
|
||||
.password("password")
|
||||
.firstName("Tom")
|
||||
.lastName("Brady").build();
|
||||
testRealm.getUsers().add(user);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
UserRepresentation user = getUser("tom");
|
||||
user.setAttributes(Map.of());
|
||||
user.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PROFILE.name()));
|
||||
testRealm().users().get(user.getId()).update(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateProfile() {
|
||||
UserRepresentation user = getUser("tom");
|
||||
Map<String, List<String>> attributes = user.getAttributes();
|
||||
assertNull(attributes);
|
||||
user = updateProfile();
|
||||
assertCustomAttributes(user.getAttributes());
|
||||
}
|
||||
|
||||
protected UserRepresentation updateProfile() {
|
||||
navigateToUpdateProfilePage();
|
||||
updateProfilePage.update(CUSTOM_ATTRIBUTES.entrySet().stream()
|
||||
.map((Function<Entry<String, String>, Entry<String, String>>) entry -> new SimpleEntry<>(Constants.USER_ATTRIBUTES_PREFIX + entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)));
|
||||
return getUser("tom");
|
||||
}
|
||||
|
||||
protected static void assertCustomAttributes(Map<String, List<String>> attributes) {
|
||||
assertNotNull(attributes);
|
||||
for (Entry<String, String> attribute : CUSTOM_ATTRIBUTES.entrySet()) {
|
||||
String name = attribute.getKey();
|
||||
List<String> values = attributes.get(name);
|
||||
assertNotNull(values);
|
||||
assertFalse(values.isEmpty());
|
||||
assertEquals(CUSTOM_ATTRIBUTES.get(name), values.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
protected UserRepresentation getUser(String username) {
|
||||
List<UserRepresentation> users = testRealm().users().search(username);
|
||||
assertFalse(users.isEmpty());
|
||||
return testRealm().users().get(users.get(0).getId()).toRepresentation();
|
||||
}
|
||||
|
||||
private void navigateToUpdateProfilePage() {
|
||||
loginPage.open();
|
||||
loginPage.login("tom@keycloak.org", "password");
|
||||
updateProfilePage.assertCurrent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2023 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.theme;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.disableDynamicUserProfile;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.enableDynamicUserProfile;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
|
||||
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
|
||||
public class CustomUpdateProfileTemplateUserProfileTest extends CustomUpdateProfileTemplateTest {
|
||||
|
||||
private UPConfig upConfig;
|
||||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
super.onBefore();
|
||||
upConfig = updateUserProfileConfiguration();
|
||||
}
|
||||
|
||||
@After
|
||||
public void onAfter() {
|
||||
disableDynamicUserProfile(testRealm());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void testUpdateProfile() {
|
||||
upConfig.setUnmanagedAttributePolicy(null);
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
super.testUpdateProfile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedAttributeEnabled() {
|
||||
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
|
||||
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
|
||||
upConfig.removeAttribute(name);
|
||||
}
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
super.testUpdateProfile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedAttributeAdminEdit() {
|
||||
upConfig.setUnmanagedAttributePolicy(null);
|
||||
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
|
||||
upConfig.removeAttribute(name);
|
||||
}
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
UserRepresentation user = updateProfile();
|
||||
assertNull(user.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedAttributeDisabled() {
|
||||
upConfig.setUnmanagedAttributePolicy(null);
|
||||
for (String name : CUSTOM_ATTRIBUTES.keySet()) {
|
||||
upConfig.removeAttribute(name);
|
||||
}
|
||||
testRealm().users().userProfile().update(upConfig);
|
||||
UserRepresentation user = updateProfile();
|
||||
assertNull(user.getAttributes());
|
||||
}
|
||||
|
||||
private UPConfig updateUserProfileConfiguration() {
|
||||
RealmRepresentation realm = testRealm().toRepresentation();
|
||||
enableDynamicUserProfile(realm);
|
||||
testRealm().update(realm);
|
||||
UPConfig upCOnfig = testRealm().users().userProfile().getConfiguration();
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("street", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("locality", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("region", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("postal_code", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
upCOnfig.addOrReplaceAttribute(new UPAttribute("country", new UPAttributePermissions(Set.of(ROLE_ADMIN), Set.of(ROLE_USER))));
|
||||
testRealm().users().userProfile().update(upCOnfig);
|
||||
return upCOnfig;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.testsuite.sssd;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
|
@ -205,7 +206,7 @@ public class SSSDUserProfileTest extends AbstractBaseSSSDTest {
|
|||
Assert.assertFalse(updateProfilePage.getFieldById(UserModel.EMAIL).isEnabled());
|
||||
Assert.assertFalse(updateProfilePage.getFieldById(UserModel.USERNAME).isEnabled());
|
||||
Assert.assertTrue(updateProfilePage.getFieldById("postal_code").isEnabled());
|
||||
updateProfilePage.prepareUpdate().otherProfileAttribute("postal_code", "123456").submit();
|
||||
updateProfilePage.prepareUpdate().otherProfileAttribute(Map.of("postal_code", "123456")).submit();
|
||||
WaitUtils.waitForPageToLoad();
|
||||
appPage.assertCurrent();
|
||||
|
||||
|
@ -257,7 +258,7 @@ public class SSSDUserProfileTest extends AbstractBaseSSSDTest {
|
|||
Assert.assertTrue(updateProfilePage.getFieldById(UserModel.EMAIL).isEnabled());
|
||||
Assert.assertTrue(updateProfilePage.getFieldById(UserModel.USERNAME).isEnabled());
|
||||
Assert.assertTrue(updateProfilePage.getFieldById("postal_code").isEnabled());
|
||||
updateProfilePage.prepareUpdate().otherProfileAttribute("postal_code", "123456").submit();
|
||||
updateProfilePage.prepareUpdate().otherProfileAttribute(Map.of("postal_code", "123456")).submit();
|
||||
WaitUtils.waitForPageToLoad();
|
||||
appPage.assertCurrent();
|
||||
|
||||
|
|
|
@ -1,83 +1,12 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','email','firstName','lastName'); section>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("loginProfileTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<#if user.editUsernameAllowed>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" name="username" value="${(user.username!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<#if user.editEmailAllowed>
|
||||
<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="${(user.email!'')}"
|
||||
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>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="firstName" name="firstName" value="${(user.firstName!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('firstName')>
|
||||
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="lastName" name="lastName" value="${(user.lastName!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('lastName')>
|
||||
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<@userProfileCommons.userProfileFormFields/>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
|
@ -88,7 +17,7 @@
|
|||
<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>
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" formnovalidate/>${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<#import "register-commons.ftl" as registerCommons>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("registerTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
|
||||
<@userProfileCommons.userProfileFormFields; callback, attribute>
|
||||
<#if callback = "afterField">
|
||||
<#-- render password fields just under the username or email (if used as username) -->
|
||||
<#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<input type="password" id="password" class="${properties.kcInputClass!}" name="password"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
/>
|
||||
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
||||
aria-controls="password" data-password-toggle
|
||||
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
||||
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
||||
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm"
|
||||
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<input type="password" id="password-confirm" class="${properties.kcInputClass!}"
|
||||
name="password-confirm"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
/>
|
||||
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
||||
aria-controls="password-confirm" data-password-toggle
|
||||
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
||||
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
||||
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<#if messagesPerField.existsError('password-confirm')>
|
||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
</@userProfileCommons.userProfileFormFields>
|
||||
|
||||
<@registerCommons.termsAcceptance/>
|
||||
|
||||
<#if recaptchaRequired??>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -1,88 +1,19 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<#import "register-commons.ftl" as registerCommons>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm','termsAccepted'); section>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("registerTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
|
||||
<@userProfileCommons.userProfileFormFields; callback, attribute>
|
||||
<#if callback = "afterField">
|
||||
<#-- render password fields just under the username or email (if used as username) -->
|
||||
<#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName"
|
||||
value="${(register.formData.firstName!'')}"
|
||||
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('firstName')>
|
||||
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName"
|
||||
value="${(register.formData.lastName!'')}"
|
||||
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('lastName')>
|
||||
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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" class="${properties.kcInputClass!}" name="email"
|
||||
value="${(register.formData.email!'')}" autocomplete="email"
|
||||
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>
|
||||
|
||||
<#if !realm.registrationEmailAsUsername>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" class="${properties.kcInputClass!}" name="username"
|
||||
value="${(register.formData.username!'')}" autocomplete="username"
|
||||
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#if passwordRequired??>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
|
@ -98,7 +29,6 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
|
@ -110,7 +40,7 @@
|
|||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm"
|
||||
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
|
@ -134,6 +64,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
</@userProfileCommons.userProfileFormFields>
|
||||
|
||||
<@registerCommons.termsAcceptance/>
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("loginProfileTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
|
||||
<@userProfileCommons.userProfileFormFields/>
|
||||
|
||||
<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" formnovalidate/>${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>
|
Loading…
Reference in a new issue