diff --git a/docs/documentation/server_admin/images/registration-form-with-required-tac.png b/docs/documentation/server_admin/images/registration-form-with-required-tac.png new file mode 100644 index 00000000000..35a194657d4 Binary files /dev/null and b/docs/documentation/server_admin/images/registration-form-with-required-tac.png differ diff --git a/docs/documentation/server_admin/images/require-tac-agreement-at-registration.png b/docs/documentation/server_admin/images/require-tac-agreement-at-registration.png new file mode 100644 index 00000000000..9038558dd84 Binary files /dev/null and b/docs/documentation/server_admin/images/require-tac-agreement-at-registration.png differ diff --git a/docs/documentation/server_admin/topics/assembly-managing-users.adoc b/docs/documentation/server_admin/topics/assembly-managing-users.adoc index db2a482fdde..303afb1b91d 100644 --- a/docs/documentation/server_admin/topics/assembly-managing-users.adoc +++ b/docs/documentation/server_admin/topics/assembly-managing-users.adoc @@ -14,6 +14,7 @@ include::users/proc-configuring-user-attributes.adoc[leveloffset=+2] include::users/con-user-registration.adoc[leveloffset=+2] include::users/proc-enabling-user-registration.adoc[leveloffset=3] include::users/proc-registering-new-user.adoc[leveloffset=3] +include::users/proc-requiring-tac-agreement-at-registration.adoc[leveloffset=3] include::users/con-required-actions.adoc[leveloffset=+2] include::users/proc-setting-required-actions.adoc[leveloffset=+3] diff --git a/docs/documentation/server_admin/topics/users/proc-requiring-tac-agreement-at-registration.adoc b/docs/documentation/server_admin/topics/users/proc-requiring-tac-agreement-at-registration.adoc new file mode 100644 index 00000000000..0a2e65f425b --- /dev/null +++ b/docs/documentation/server_admin/topics/users/proc-requiring-tac-agreement-at-registration.adoc @@ -0,0 +1,24 @@ +// Module included in the following assemblies: +// +// con-user-registration.adoc + +[id="proc-requiring-tac-agreement-at-registration_{context}"] += Requiring user to agree to terms and conditions during registration + +[role="_abstract"] +For a user to register, you can require agreement to your terms and conditions. + +.Registration form with required terms and conditions agreement +image:images/registration-form-with-required-tac.png[] + +.Prerequisite +* User registration is enabled. +* Terms and conditions required action is enabled. + +.Procedure +. Click the *Flows* tab. +. Click the *registration* flow. +. Select *Required* on the *Terms and Conditions* row. ++ +.Make the terms and conditions agreement required at registration +image:images/require-tac-agreement-at-registration.png[] diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 86effa1a60f..3bde378fa03 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -51,7 +51,7 @@ public class DefaultAuthenticationFlows { public static void addFlows(RealmModel realm) { if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm); if (realm.getFlowByAlias(DIRECT_GRANT_FLOW) == null) directGrantFlow(realm, false); - if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm); + if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm, false); if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm); if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm); if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false); @@ -61,7 +61,7 @@ public class DefaultAuthenticationFlows { public static void migrateFlows(RealmModel realm) { if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true); if (realm.getFlowByAlias(DIRECT_GRANT_FLOW) == null) directGrantFlow(realm, true); - if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm); + if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm, true); if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm); if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm); if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true); @@ -69,7 +69,7 @@ public class DefaultAuthenticationFlows { if (realm.getFlowByAlias(DOCKER_AUTH) == null) dockerAuthenticationFlow(realm); } - public static void registrationFlow(RealmModel realm) { + public static void registrationFlow(RealmModel realm, boolean migrate) { AuthenticationFlowModel registrationFlow = new AuthenticationFlowModel(); registrationFlow.setAlias(REGISTRATION_FLOW); registrationFlow.setDescription("registration flow"); @@ -137,6 +137,16 @@ public class DefaultAuthenticationFlows { execution.setAuthenticatorFlow(false); //execution.setAuthenticatorConfig(captchaConfig.getId()); realm.addAuthenticatorExecution(execution); + + if (!migrate) { + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(registrationFormFlow.getId()); + execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED); + execution.setAuthenticator("registration-terms-and-conditions"); + execution.setPriority(70); + execution.setAuthenticatorFlow(false); + realm.addAuthenticatorExecution(execution); + } } public static void browserFlow(RealmModel realm) { diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationTermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationTermsAndConditions.java new file mode 100644 index 00000000000..df8eebd72ac --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationTermsAndConditions.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authentication.forms; + +import java.util.Collections; +import java.util.List; +import jakarta.ws.rs.core.MultivaluedMap; +import org.keycloak.Config; +import org.keycloak.authentication.FormAction; +import org.keycloak.authentication.FormActionFactory; +import org.keycloak.authentication.FormContext; +import org.keycloak.authentication.ValidationContext; +import org.keycloak.events.Errors; +import org.keycloak.forms.login.LoginFormsProvider; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.FormMessage; +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.ProviderConfigProperty; + +public class RegistrationTermsAndConditions implements FormAction, FormActionFactory, ConfiguredProvider { + + public static final String PROVIDER_ID = "registration-terms-and-conditions"; + + protected static final String FIELD = "termsAccepted"; + + @Override + public String getDisplayType() { + return "Terms and conditions"; + } + + @Override + public String getReferenceCategory() { + return "terms-and-conditions"; + } + + @Override + public boolean isConfigurable() { + return false; + } + + private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { + AuthenticationExecutionModel.Requirement.REQUIRED, + AuthenticationExecutionModel.Requirement.DISABLED + }; + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + @Override + public void buildPage(FormContext context, LoginFormsProvider form) { + form.setAttribute("termsAcceptanceRequired", true); + } + + @Override + public void validate(ValidationContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + if (formData.containsKey(FIELD)) { + context.success(); + return; + } + + context.error(Errors.INVALID_REGISTRATION); + context.validationError(formData, Collections.singletonList(new FormMessage(FIELD, "termsAcceptanceRequired"))); + } + + @Override + public void success(FormContext context) { + + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public String getHelpText() { + return "Asks the user to accept terms and conditions before submitting its registration form."; + } + + @Override + public List getConfigProperties() { + return Collections.emptyList(); + } + + @Override + public FormAction create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory index 8b10d73bca4..5287a11c1fc 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.FormActionFactory @@ -18,4 +18,5 @@ org.keycloak.authentication.forms.RegistrationPassword org.keycloak.authentication.forms.RegistrationProfile org.keycloak.authentication.forms.RegistrationUserCreation -org.keycloak.authentication.forms.RegistrationRecaptcha \ No newline at end of file +org.keycloak.authentication.forms.RegistrationRecaptcha +org.keycloak.authentication.forms.RegistrationTermsAndConditions diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AccountFields.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AccountFields.java index 2e45ad4026e..197472a2abb 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AccountFields.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AccountFields.java @@ -162,6 +162,9 @@ public class AccountFields extends FieldsBase { @FindBy(id = "input-error-username") private WebElement usernameError; + @FindBy(id = "input-error-terms-accepted") + private WebElement termsError; + public String getFirstNameError() { try { return getTextFromElement(firstNameError); @@ -201,5 +204,13 @@ public class AccountFields extends FieldsBase { return null; } } + + public String getTermsError(){ + try { + return getTextFromElement(termsError); + } catch (NoSuchElementException e) { + return null; + } + } } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java index 3505b9ca4e9..4b35ff5d332 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/RegisterPage.java @@ -56,10 +56,13 @@ public class RegisterPage extends AbstractPage { @FindBy(id = "password-confirm") private WebElement passwordConfirmInput; - + @FindBy(id = "department") private WebElement departmentInput; + @FindBy(id = "termsAccepted") + private WebElement termsAcceptedInput; + @FindBy(css = "input[type=\"submit\"]") private WebElement submitButton; @@ -77,6 +80,10 @@ public class RegisterPage extends AbstractPage { } 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); + } + + public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm, String department, Boolean termsAccepted) { firstNameInput.clear(); if (firstName != null) { firstNameInput.sendKeys(firstName); @@ -114,6 +121,15 @@ public class RegisterPage extends AbstractPage { } } + try { + termsAcceptedInput.clear(); + } catch (NoSuchElementException e) { + // ignore + } + if (termsAccepted != null && termsAccepted) { + termsAcceptedInput.click(); + } + submitButton.click(); } @@ -173,7 +189,7 @@ public class RegisterPage extends AbstractPage { } return null; } - + public String getLabelForField(String fieldId) { return driver.findElement(By.cssSelector("label[for="+fieldId+"]")).getText(); } @@ -218,7 +234,7 @@ public class RegisterPage extends AbstractPage { } } - + public boolean isCurrent() { return PageUtils.getPageTitle(driver).equals("Register"); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java index 10cac51361b..3dc3f529860 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java @@ -70,6 +70,8 @@ public class ProvidersTest extends AbstractAuthenticationTest { addProviderInfo(expected, "registration-user-creation", "Registration User Creation", "This action must always be first! Validates the username of the user in validation phase. " + "In success phase, this will create the user in the database."); + addProviderInfo(expected, "registration-terms-and-conditions", "Terms and conditions", + "Asks the user to accept terms and conditions before submitting its registration form."); compareProviders(expected, result); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index 548d2dc5699..cff5c589f62 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -27,6 +27,7 @@ import org.keycloak.authentication.authenticators.browser.CookieAuthenticatorFac import org.keycloak.authentication.forms.RegistrationPassword; import org.keycloak.authentication.forms.RegistrationProfile; import org.keycloak.authentication.forms.RegistrationRecaptcha; +import org.keycloak.authentication.forms.RegistrationTermsAndConditions; import org.keycloak.authentication.forms.RegistrationUserCreation; import org.keycloak.events.Details; import org.keycloak.events.EventType; @@ -34,6 +35,7 @@ import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.pages.AppPage; @@ -52,6 +54,7 @@ import org.keycloak.testsuite.util.AccountHelper; import jakarta.mail.internet.MimeMessage; import jakarta.ws.rs.core.Response; import java.io.IOException; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.anyOf; @@ -353,7 +356,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest { //contains few special characters we want to be sure they are allowed in username String username = "register.U-se@rS_uccess"; - + registerPage.register("firstName", "lastName", "registerUserSuccess@email", username, "password", "password"); appPage.assertCurrent(); @@ -644,6 +647,56 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest { registerPage.assertCurrent(); } + //KEYCLOAK-15244 + @Test + public void registerUserMissingTermsAcceptance() { + configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString(), + AuthenticationExecutionModel.Requirement.REQUIRED); + + try { + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("firstName", "lastName", "registerUserMissingTermsAcceptance@email", + "registerUserMissingTermsAcceptance", "password", "password", null, false); + + registerPage.assertCurrent(); + assertEquals("You must agree to our terms and conditions.", registerPage.getInputAccountErrors().getTermsError()); + + events.expectRegister("registerUserMissingTermsAcceptance", "registerUserMissingTermsAcceptance@email") + .removeDetail(Details.USERNAME) + .removeDetail(Details.EMAIL) + .error("invalid_registration").assertEvent(); + } finally { + configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString()); + } + } + + //KEYCLOAK-15244 + @Test + public void registerUserSuccessTermsAcceptance() { + configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString(), + AuthenticationExecutionModel.Requirement.REQUIRED); + + try { + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("firstName", "lastName", "registerUserSuccessTermsAcceptance@email", + "registerUserSuccessTermsAcceptance", "password", "password", null, true); + + assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + String userId = events.expectRegister("registerUserSuccessTermsAcceptance", "registerUserSuccessTermsAcceptance@email") + .assertEvent().getUserId(); + assertUserRegistered(userId, "registerUserSuccessTermsAcceptance", "registerUserSuccessTermsAcceptance@email"); + } finally { + configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString()); + } + } + protected RealmAttributeUpdater configureRealmRegistrationEmailAsUsername(final boolean value) { return getRealmAttributeUpdater().setRegistrationEmailAsUsername(value); } @@ -709,19 +762,24 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest { } private void configureRegistrationFlowWithCustomRegistrationPageForm(String newFlowAlias) { + configureRegistrationFlowWithCustomRegistrationPageForm(newFlowAlias, AuthenticationExecutionModel.Requirement.DISABLED); + } + + private void configureRegistrationFlowWithCustomRegistrationPageForm(String newFlowAlias, AuthenticationExecutionModel.Requirement termsAndConditionRequirement) { testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyRegistrationFlow(newFlowAlias)); testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session) .selectFlow(newFlowAlias) - .clear() - .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.ALTERNATIVE, CookieAuthenticatorFactory.PROVIDER_ID) - .addSubFlowExecution("Sub Flow", AuthenticationFlow.BASIC_FLOW, AuthenticationExecutionModel.Requirement.ALTERNATIVE, subflow -> subflow - .addSubFlowExecution("Sub sub Form Flow", AuthenticationFlow.FORM_FLOW, AuthenticationExecutionModel.Requirement.REQUIRED, subsubflow -> subsubflow - .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RegistrationUserCreation.PROVIDER_ID) - .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RegistrationProfile.PROVIDER_ID) - .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RegistrationPassword.PROVIDER_ID) - .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.DISABLED, RegistrationRecaptcha.PROVIDER_ID) - ) + .clear() + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.ALTERNATIVE, CookieAuthenticatorFactory.PROVIDER_ID) + .addSubFlowExecution("Sub Flow", AuthenticationFlow.BASIC_FLOW, AuthenticationExecutionModel.Requirement.ALTERNATIVE, subflow -> subflow + .addSubFlowExecution("Sub sub Form Flow", AuthenticationFlow.FORM_FLOW, AuthenticationExecutionModel.Requirement.REQUIRED, subsubflow -> subsubflow + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RegistrationUserCreation.PROVIDER_ID) + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RegistrationProfile.PROVIDER_ID) + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, RegistrationPassword.PROVIDER_ID) + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.DISABLED, RegistrationRecaptcha.PROVIDER_ID) + .addAuthenticatorExecution(termsAndConditionRequirement, RegistrationTermsAndConditions.PROVIDER_ID) ) + ) .defineAsRegistrationFlow() // Activate this new flow ); } diff --git a/themes/src/main/resources-community/theme/base/login/messages/messages_fr.properties b/themes/src/main/resources-community/theme/base/login/messages/messages_fr.properties index 44ffe65b276..c1478a31b45 100644 --- a/themes/src/main/resources-community/theme/base/login/messages/messages_fr.properties +++ b/themes/src/main/resources-community/theme/base/login/messages/messages_fr.properties @@ -63,6 +63,8 @@ termsTitle=Termes et Conditions termsTitleHtml=Termes et Conditions termsText=

Termes et conditions \u00e0 d\u00e9finir

termsPlainText=Termes et conditions \u00e0 d\u00e9finir +termsAcceptanceRequired=Vous devez accepter les termes et conditions. +acceptTerms=J''accepte les termes et conditions recaptchaFailed=Re-captcha invalide recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configur\u00e9 diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 27897bb9b92..f6afa26f4ee 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -68,6 +68,8 @@ unknown=Unknown termsTitle=Terms and Conditions termsText=

Terms and conditions to be defined

termsPlainText=Terms and conditions to be defined. +termsAcceptanceRequired=You must agree to our terms and conditions. +acceptTerms=I agree to the terms and conditions recaptchaFailed=Invalid Recaptcha recaptchaNotConfigured=Recaptcha is required, but not configured diff --git a/themes/src/main/resources/theme/base/login/register-commons.ftl b/themes/src/main/resources/theme/base/login/register-commons.ftl new file mode 100644 index 00000000000..7007797ee0e --- /dev/null +++ b/themes/src/main/resources/theme/base/login/register-commons.ftl @@ -0,0 +1,27 @@ +<#macro termsAcceptance> + <#if termsAcceptanceRequired??> +
+
+ ${msg("termsTitle")} +
+ ${kcSanitize(msg("termsText"))?no_esc} +
+
+
+
+
+ + +
+ <#if messagesPerField.existsError('termsAccepted')> +
+ + ${kcSanitize(messagesPerField.get('termsAccepted'))?no_esc} + +
+ +
+ + diff --git a/themes/src/main/resources/theme/base/login/register-user-profile.ftl b/themes/src/main/resources/theme/base/login/register-user-profile.ftl index e0d533b89f5..fe9cdf607fe 100755 --- a/themes/src/main/resources/theme/base/login/register-user-profile.ftl +++ b/themes/src/main/resources/theme/base/login/register-user-profile.ftl @@ -1,5 +1,6 @@ <#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")} @@ -49,6 +50,8 @@ + + <@registerCommons.termsAcceptance/> <#if recaptchaRequired??>
@@ -71,4 +74,4 @@
- \ No newline at end of file + diff --git a/themes/src/main/resources/theme/base/login/register.ftl b/themes/src/main/resources/theme/base/login/register.ftl index db509847982..c06de23babd 100755 --- a/themes/src/main/resources/theme/base/login/register.ftl +++ b/themes/src/main/resources/theme/base/login/register.ftl @@ -1,5 +1,6 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section> +<#import "register-commons.ftl" as registerCommons> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm','termsAccepted'); section> <#if section = "header"> ${msg("registerTitle")} <#elseif section = "form"> @@ -117,6 +118,8 @@ + <@registerCommons.termsAcceptance/> + <#if recaptchaRequired??>
@@ -138,4 +141,4 @@
- \ No newline at end of file + diff --git a/themes/src/main/resources/theme/keycloak/login/resources/css/login.css b/themes/src/main/resources/theme/keycloak/login/resources/css/login.css index 7ca83002fb1..7bdeef82209 100644 --- a/themes/src/main/resources/theme/keycloak/login/resources/css/login.css +++ b/themes/src/main/resources/theme/keycloak/login/resources/css/login.css @@ -178,6 +178,13 @@ div.kc-logo-text span { margin-bottom: 20px; } +#kc-registration-terms-text { + max-height: 100px; + overflow-y: auto; + overflow-x: hidden; + margin: 5px; +} + #kc-registration { margin-bottom: 0; }