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.
|
configuration set to a realm.
|
||||||
|
|
||||||
For more details, see link:{upgradingguide_link}[{upgradingguide_name}].
|
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
|
= `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.
|
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, LOGIN_USERNAME, LOGIN_PASSWORD, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_WEBAUTHN, LOGIN_VERIFY_EMAIL,
|
||||||
LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_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_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,
|
LOGIN_RECOVERY_AUTHN_CODES_INPUT, LOGIN_RECOVERY_AUTHN_CODES_CONFIG,
|
||||||
FRONTCHANNEL_LOGOUT, LOGOUT_CONFIRM, UPDATE_EMAIL, LOGIN_RESET_OTP;
|
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) {
|
if (attributes != null) {
|
||||||
for (Map.Entry<String, ?> entry : attributes.entrySet()) {
|
for (Map.Entry<String, ?> entry : attributes.entrySet()) {
|
||||||
String key = entry.getKey();
|
String name = entry.getKey();
|
||||||
|
|
||||||
if (!isSupportedAttribute(key)) {
|
if (!isSupportedAttribute(name)) {
|
||||||
if (!isManagedAttribute(key) && isAllowUnmanagedAttribute()) {
|
if (!isManagedAttribute(name) && isAllowUnmanagedAttribute()) {
|
||||||
unmanagedAttributes.put(key, normalizeAttributeValues(key, entry.getValue()));
|
String normalizedName = normalizeAttributeName(name);
|
||||||
|
unmanagedAttributes.put(normalizedName, normalizeAttributeValues(normalizedName, entry.getValue()));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
|
String normalizedName = normalizeAttributeName(name);
|
||||||
key = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
|
List<String> values = normalizeAttributeValues(normalizedName, entry.getValue());
|
||||||
}
|
|
||||||
|
|
||||||
List<String> values = normalizeAttributeValues(key, entry.getValue());
|
newAttributes.put(normalizedName, Collections.unmodifiableList(values));
|
||||||
|
|
||||||
newAttributes.put(key, Collections.unmodifiableList(values));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,6 +396,13 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
||||||
return newAttributes;
|
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) {
|
private List<String> normalizeAttributeValues(String name, Object value) {
|
||||||
List<String> values;
|
List<String> values;
|
||||||
|
|
||||||
|
@ -472,7 +477,7 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isManagedAttribute(String name) {
|
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);
|
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
|
@Override
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return userCtx.getUsername();
|
return userCtx.getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsername(String username) {
|
||||||
|
userCtx.setUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getServiceAccountClientLink() {
|
public String getServiceAccountClientLink() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -91,7 +91,6 @@ import org.keycloak.forms.login.MessageType;
|
||||||
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
||||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||||
import org.keycloak.userprofile.UserProfileContext;
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,13 +160,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
break;
|
break;
|
||||||
case UPDATE_PROFILE:
|
case UPDATE_PROFILE:
|
||||||
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, new UserUpdateProfileContext(realm, user));
|
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, new UserUpdateProfileContext(realm, user));
|
||||||
|
|
||||||
actionMessage = Messages.UPDATE_PROFILE;
|
actionMessage = Messages.UPDATE_PROFILE;
|
||||||
if(isDynamicUserProfile()) {
|
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||||
page = LoginFormsPages.UPDATE_USER_PROFILE;
|
|
||||||
} else {
|
|
||||||
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case UPDATE_EMAIL:
|
case UPDATE_EMAIL:
|
||||||
actionMessage = Messages.UPDATE_EMAIL;
|
actionMessage = Messages.UPDATE_EMAIL;
|
||||||
|
@ -187,9 +181,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
case VERIFY_PROFILE:
|
case VERIFY_PROFILE:
|
||||||
UpdateProfileContext verifyProfile = new UserUpdateProfileContext(realm, user);
|
UpdateProfileContext verifyProfile = new UserUpdateProfileContext(realm, user);
|
||||||
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, verifyProfile);
|
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, verifyProfile);
|
||||||
|
|
||||||
actionMessage = Messages.UPDATE_PROFILE;
|
actionMessage = Messages.UPDATE_PROFILE;
|
||||||
page = LoginFormsPages.UPDATE_USER_PROFILE;
|
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return Response.serverError().build();
|
return Response.serverError().build();
|
||||||
|
@ -247,6 +240,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
attributes.put("recoveryAuthnCodesInputBean", new RecoveryAuthnCodeInputLoginBean(session, realm, user));
|
attributes.put("recoveryAuthnCodesInputBean", new RecoveryAuthnCodeInputLoginBean(session, realm, user));
|
||||||
break;
|
break;
|
||||||
case LOGIN_UPDATE_PROFILE:
|
case LOGIN_UPDATE_PROFILE:
|
||||||
|
attributes.put("profile", new VerifyProfileBean(user, formData, session));
|
||||||
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||||
attributes.put("user", new ProfileBean(userCtx, formData));
|
attributes.put("user", new ProfileBean(userCtx, formData));
|
||||||
break;
|
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)));
|
attributes.put("configuredOtpCredentials", new TotpLoginBean(session, realm, user, (String) this.attributes.get(OTPFormAuthenticator.SELECTED_OTP_CREDENTIAL_ID)));
|
||||||
break;
|
break;
|
||||||
case REGISTER:
|
case REGISTER:
|
||||||
if(isDynamicUserProfile()) {
|
|
||||||
page = LoginFormsPages.REGISTER_USER_PROFILE;
|
|
||||||
}
|
|
||||||
RegisterBean rb = new RegisterBean(formData,session);
|
RegisterBean rb = new RegisterBean(formData,session);
|
||||||
//legacy bean for static template
|
//legacy bean for static template
|
||||||
attributes.put("register", rb);
|
attributes.put("register", rb);
|
||||||
|
@ -297,11 +288,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
case SAML_POST_FORM:
|
case SAML_POST_FORM:
|
||||||
attributes.put("samlPost", new SAMLPostFormBean(formData));
|
attributes.put("samlPost", new SAMLPostFormBean(formData));
|
||||||
break;
|
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:
|
case IDP_REVIEW_USER_PROFILE:
|
||||||
UpdateProfileContext idpCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
UpdateProfileContext idpCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||||
attributes.put("profile", new IdpReviewProfileBean(idpCtx, formData, session));
|
attributes.put("profile", new IdpReviewProfileBean(idpCtx, formData, session));
|
||||||
|
@ -317,10 +303,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
return processTemplate(theme, Templates.getTemplate(page), locale);
|
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.
|
* Get sure that correct hostname and path is used for totp form.
|
||||||
* Relevant when running in proxy mode.
|
* Relevant when running in proxy mode.
|
||||||
|
@ -646,15 +628,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
setMessage(MessageType.WARNING, Messages.UPDATE_PROFILE);
|
setMessage(MessageType.WARNING, Messages.UPDATE_PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isDynamicUserProfile()) {
|
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
|
||||||
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);
|
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
|
@Override
|
||||||
|
|
|
@ -62,8 +62,6 @@ public class Templates {
|
||||||
return "select-authenticator.ftl";
|
return "select-authenticator.ftl";
|
||||||
case REGISTER:
|
case REGISTER:
|
||||||
return "register.ftl";
|
return "register.ftl";
|
||||||
case REGISTER_USER_PROFILE:
|
|
||||||
return "register-user-profile.ftl";
|
|
||||||
case INFO:
|
case INFO:
|
||||||
return "info.ftl";
|
return "info.ftl";
|
||||||
case ERROR:
|
case ERROR:
|
||||||
|
@ -82,8 +80,6 @@ public class Templates {
|
||||||
return "login-x509-info.ftl";
|
return "login-x509-info.ftl";
|
||||||
case SAML_POST_FORM:
|
case SAML_POST_FORM:
|
||||||
return "saml-post-form.ftl";
|
return "saml-post-form.ftl";
|
||||||
case UPDATE_USER_PROFILE:
|
|
||||||
return "update-user-profile.ftl";
|
|
||||||
case IDP_REVIEW_USER_PROFILE:
|
case IDP_REVIEW_USER_PROFILE:
|
||||||
return "idp-review-user-profile.ftl";
|
return "idp-review-user-profile.ftl";
|
||||||
case FRONTCHANNEL_LOGOUT:
|
case FRONTCHANNEL_LOGOUT:
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class LegacyAttributes extends DefaultAttributes {
|
||||||
@Override
|
@Override
|
||||||
public Map<String, List<String>> getReadable() {
|
public Map<String, List<String>> getReadable() {
|
||||||
if(user == null || user.getAttributes() == null) {
|
if(user == null || user.getAttributes() == null) {
|
||||||
return Collections.emptyMap();
|
return super.getReadable();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HashMap<>(user.getAttributes());
|
return new HashMap<>(user.getAttributes());
|
||||||
|
|
|
@ -22,6 +22,7 @@ import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.keycloak.testsuite.util.UIUtils;
|
import org.keycloak.testsuite.util.UIUtils;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
|
@ -66,6 +67,10 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
||||||
prepareUpdate().firstName(firstName).lastName(lastName).email(email).submit();
|
prepareUpdate().firstName(firstName).lastName(lastName).email(email).submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void update(Map<String, String> attributes) {
|
||||||
|
prepareUpdate().otherProfileAttribute(attributes).submit();
|
||||||
|
}
|
||||||
|
|
||||||
public Update prepareUpdate() {
|
public Update prepareUpdate() {
|
||||||
return new Update(this);
|
return new Update(this);
|
||||||
}
|
}
|
||||||
|
@ -176,8 +181,8 @@ public class LoginUpdateProfilePage extends AbstractPage {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Update otherProfileAttribute(String name, String value) {
|
public Update otherProfileAttribute(Map<String, String> attributes) {
|
||||||
other.put(name, value);
|
other.putAll(attributes);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.testsuite.auth.page.AccountFields;
|
import org.keycloak.testsuite.auth.page.AccountFields;
|
||||||
import org.keycloak.testsuite.auth.page.PasswordFields;
|
import org.keycloak.testsuite.auth.page.PasswordFields;
|
||||||
|
@ -76,14 +80,18 @@ public class RegisterPage extends AbstractPage {
|
||||||
private WebElement backToLoginLink;
|
private WebElement backToLoginLink;
|
||||||
|
|
||||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
|
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) {
|
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();
|
firstNameInput.clear();
|
||||||
if (firstName != null) {
|
if (firstName != null) {
|
||||||
firstNameInput.sendKeys(firstName);
|
firstNameInput.sendKeys(firstName);
|
||||||
|
@ -125,6 +133,12 @@ public class RegisterPage extends AbstractPage {
|
||||||
termsAcceptedInput.click();
|
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();
|
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
|
driver.getPageSource(), containsString("Jelentkezzen be a fiókjába")); // Sign in to your account
|
||||||
|
|
||||||
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
|
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.",
|
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",
|
assertThat("We must be on correct realm right now",
|
||||||
driver.getCurrentUrl(), containsString("/auth/realms/" + bc.consumerRealmName() + "/"));
|
driver.getCurrentUrl(), containsString("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
|
|
@ -727,7 +727,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "registerUserMissingTermsAcceptance@email",
|
registerPage.register("firstName", "lastName", "registerUserMissingTermsAcceptance@email",
|
||||||
"registerUserMissingTermsAcceptance", "password", "password", null, false);
|
"registerUserMissingTermsAcceptance", "password", "password", null, false, null);
|
||||||
|
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
assertEquals("You must agree to our terms and conditions.", registerPage.getInputAccountErrors().getTermsError());
|
assertEquals("You must agree to our terms and conditions.", registerPage.getInputAccountErrors().getTermsError());
|
||||||
|
@ -753,7 +753,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "registerUserSuccessTermsAcceptance@email",
|
registerPage.register("firstName", "lastName", "registerUserSuccessTermsAcceptance@email",
|
||||||
"registerUserSuccessTermsAcceptance", "password", "password", null, true);
|
"registerUserSuccessTermsAcceptance", "password", "password", null, true, null);
|
||||||
|
|
||||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
|
|
@ -540,7 +540,7 @@ public class RegisterWithUserProfileTest extends RegisterTest {
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertTrue(registerPage.isDepartmentPresent());
|
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.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
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;
|
package org.keycloak.testsuite.sssd;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Assert;
|
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.EMAIL).isEnabled());
|
||||||
Assert.assertFalse(updateProfilePage.getFieldById(UserModel.USERNAME).isEnabled());
|
Assert.assertFalse(updateProfilePage.getFieldById(UserModel.USERNAME).isEnabled());
|
||||||
Assert.assertTrue(updateProfilePage.getFieldById("postal_code").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();
|
WaitUtils.waitForPageToLoad();
|
||||||
appPage.assertCurrent();
|
appPage.assertCurrent();
|
||||||
|
|
||||||
|
@ -257,7 +258,7 @@ public class SSSDUserProfileTest extends AbstractBaseSSSDTest {
|
||||||
Assert.assertTrue(updateProfilePage.getFieldById(UserModel.EMAIL).isEnabled());
|
Assert.assertTrue(updateProfilePage.getFieldById(UserModel.EMAIL).isEnabled());
|
||||||
Assert.assertTrue(updateProfilePage.getFieldById(UserModel.USERNAME).isEnabled());
|
Assert.assertTrue(updateProfilePage.getFieldById(UserModel.USERNAME).isEnabled());
|
||||||
Assert.assertTrue(updateProfilePage.getFieldById("postal_code").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();
|
WaitUtils.waitForPageToLoad();
|
||||||
appPage.assertCurrent();
|
appPage.assertCurrent();
|
||||||
|
|
||||||
|
|
|
@ -1,83 +1,12 @@
|
||||||
<#import "template.ftl" as layout>
|
<#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">
|
<#if section = "header">
|
||||||
${msg("loginProfileTitle")}
|
${msg("loginProfileTitle")}
|
||||||
<#elseif section = "form">
|
<#elseif section = "form">
|
||||||
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
<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')>
|
<@userProfileCommons.userProfileFormFields/>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||||
|
@ -87,10 +16,10 @@
|
||||||
|
|
||||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||||
<#if isAppInitiatedAction??>
|
<#if isAppInitiatedAction??>
|
||||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
<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>
|
<#else>
|
||||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||||
</#if>
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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,139 +1,71 @@
|
||||||
<#import "template.ftl" as layout>
|
<#import "template.ftl" as layout>
|
||||||
|
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||||
<#import "register-commons.ftl" as registerCommons>
|
<#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">
|
<#if section = "header">
|
||||||
${msg("registerTitle")}
|
${msg("registerTitle")}
|
||||||
<#elseif section = "form">
|
<#elseif section = "form">
|
||||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||||
<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')>
|
<@userProfileCommons.userProfileFormFields; callback, attribute>
|
||||||
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
<#if callback = "afterField">
|
||||||
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
|
<#-- render password fields just under the username or email (if used as username) -->
|
||||||
</span>
|
<#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
|
||||||
</#if>
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
</div>
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
<#if messagesPerField.existsError('password')>
|
||||||
<div class="${properties.kcLabelWrapperClass!}">
|
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||||
</div>
|
</span>
|
||||||
<div class="${properties.kcInputWrapperClass!}">
|
</#if>
|
||||||
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName"
|
</div>
|
||||||
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>
|
|
||||||
</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>
|
</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')>
|
<#if messagesPerField.existsError('password-confirm')>
|
||||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||||
</span>
|
</span>
|
||||||
</#if>
|
</#if>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
</#if>
|
||||||
<#if messagesPerField.existsError('password-confirm')>
|
</#if>
|
||||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
</@userProfileCommons.userProfileFormFields>
|
||||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
|
||||||
</span>
|
|
||||||
</#if>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<@registerCommons.termsAcceptance/>
|
<@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