Integrate registration with terms and conditions required action
Closes #25891 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
a8eca6add0
commit
e162974a8d
4 changed files with 76 additions and 4 deletions
|
@ -2983,3 +2983,4 @@ viewGuides=View guides
|
||||||
joinCommunity=Join community
|
joinCommunity=Join community
|
||||||
readBlog=Read blog
|
readBlog=Read blog
|
||||||
customValue=Custom value
|
customValue=Custom value
|
||||||
|
termsAndConditionsUserAttribute=Terms and conditions accepted timestamp
|
||||||
|
|
|
@ -25,6 +25,8 @@ import org.keycloak.authentication.FormAction;
|
||||||
import org.keycloak.authentication.FormActionFactory;
|
import org.keycloak.authentication.FormActionFactory;
|
||||||
import org.keycloak.authentication.FormContext;
|
import org.keycloak.authentication.FormContext;
|
||||||
import org.keycloak.authentication.ValidationContext;
|
import org.keycloak.authentication.ValidationContext;
|
||||||
|
import org.keycloak.authentication.requiredactions.TermsAndConditions;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
@ -33,6 +35,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
@ -137,6 +140,17 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
|
||||||
|
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
|
|
||||||
|
if ("on".equals(formData.getFirst(RegistrationTermsAndConditions.FIELD))) {
|
||||||
|
// if accepted terms and conditions checkbox, remove action and add the attribute if enabled
|
||||||
|
RequiredActionProviderModel tacModel = context.getRealm().getRequiredActionProviderByAlias(
|
||||||
|
UserModel.RequiredAction.TERMS_AND_CONDITIONS.name());
|
||||||
|
if (tacModel != null && tacModel.isEnabled()) {
|
||||||
|
user.setSingleAttribute(TermsAndConditions.USER_ATTRIBUTE, Integer.toString(Time.currentTime()));
|
||||||
|
context.getAuthenticationSession().removeRequiredAction(UserModel.RequiredAction.TERMS_AND_CONDITIONS);
|
||||||
|
user.removeRequiredAction(UserModel.RequiredAction.TERMS_AND_CONDITIONS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context.setUser(user);
|
context.setUser(user);
|
||||||
|
|
||||||
context.getAuthenticationSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
|
context.getAuthenticationSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authentication.requiredactions.TermsAndConditions;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.component.AmphibianProviderFactory;
|
import org.keycloak.component.AmphibianProviderFactory;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
@ -38,6 +39,7 @@ import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||||
|
@ -173,6 +175,13 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
|
||||||
return realm.isInternationalizationEnabled();
|
return realm.isInternationalizationEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isTermAndConditionsEnabled(AttributeContext context) {
|
||||||
|
RealmModel realm = context.getSession().getContext().getRealm();
|
||||||
|
RequiredActionProviderModel tacModel = realm.getRequiredActionProviderByAlias(
|
||||||
|
UserModel.RequiredAction.TERMS_AND_CONDITIONS.name());
|
||||||
|
return tacModel != null && tacModel.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isNewUser(AttributeContext c) {
|
private static boolean isNewUser(AttributeContext c) {
|
||||||
return c.getUser() == null;
|
return c.getUser() == null;
|
||||||
}
|
}
|
||||||
|
@ -440,6 +449,11 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
|
||||||
metadata.addAttribute(UserModel.LOCALE, -1, DeclarativeUserProfileProviderFactory::isInternationalizationEnabled, DeclarativeUserProfileProviderFactory::isInternationalizationEnabled)
|
metadata.addAttribute(UserModel.LOCALE, -1, DeclarativeUserProfileProviderFactory::isInternationalizationEnabled, DeclarativeUserProfileProviderFactory::isInternationalizationEnabled)
|
||||||
.setRequired(AttributeMetadata.ALWAYS_FALSE);
|
.setRequired(AttributeMetadata.ALWAYS_FALSE);
|
||||||
|
|
||||||
|
metadata.addAttribute(TermsAndConditions.USER_ATTRIBUTE, -1, AttributeMetadata.ALWAYS_FALSE,
|
||||||
|
DeclarativeUserProfileProviderFactory::isTermAndConditionsEnabled)
|
||||||
|
.setAttributeDisplayName("${termsAndConditionsUserAttribute}")
|
||||||
|
.setRequired(AttributeMetadata.ALWAYS_FALSE);
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.testsuite.forms;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Assume;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.authentication.AuthenticationFlow;
|
import org.keycloak.authentication.AuthenticationFlow;
|
||||||
|
@ -28,14 +27,18 @@ import org.keycloak.authentication.forms.RegistrationPassword;
|
||||||
import org.keycloak.authentication.forms.RegistrationRecaptcha;
|
import org.keycloak.authentication.forms.RegistrationRecaptcha;
|
||||||
import org.keycloak.authentication.forms.RegistrationTermsAndConditions;
|
import org.keycloak.authentication.forms.RegistrationTermsAndConditions;
|
||||||
import org.keycloak.authentication.forms.RegistrationUserCreation;
|
import org.keycloak.authentication.forms.RegistrationUserCreation;
|
||||||
|
import org.keycloak.authentication.requiredactions.TermsAndConditions;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
@ -436,7 +439,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
||||||
assertUserRegistered(userId, username, email);
|
assertUserRegistered(userId, username, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertUserRegistered(String userId, String username, String email) {
|
private UserRepresentation assertUserRegistered(String userId, String username, String email) {
|
||||||
events.expectLogin().detail("username", username.toLowerCase()).user(userId).assertEvent();
|
events.expectLogin().detail("username", username.toLowerCase()).user(userId).assertEvent();
|
||||||
|
|
||||||
UserRepresentation user = getUser(userId);
|
UserRepresentation user = getUser(userId);
|
||||||
|
@ -445,6 +448,7 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
||||||
// test that timestamp is current with 10s tollerance
|
// test that timestamp is current with 10s tollerance
|
||||||
assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
|
assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
|
||||||
assertUserBasicRegisterAttributes(userId, username, email, "firstName", "lastName");
|
assertUserBasicRegisterAttributes(userId, username, email, "firstName", "lastName");
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -761,12 +765,51 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
String userId = events.expectRegister("registerUserSuccessTermsAcceptance", "registerUserSuccessTermsAcceptance@email")
|
String userId = events.expectRegister("registerUserSuccessTermsAcceptance", "registerUserSuccessTermsAcceptance@email")
|
||||||
.assertEvent().getUserId();
|
.assertEvent().getUserId();
|
||||||
assertUserRegistered(userId, "registerUserSuccessTermsAcceptance", "registerUserSuccessTermsAcceptance@email");
|
UserRepresentation user = assertUserRegistered(userId, "registerUserSuccessTermsAcceptance", "registerUserSuccessTermsAcceptance@email");
|
||||||
|
Assert.assertNull(user.getAttributes());
|
||||||
} finally {
|
} finally {
|
||||||
configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString());
|
configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void registerUserSuccessTermsAcceptanceWithRequiredActionEnabled() {
|
||||||
|
configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString(),
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
|
||||||
|
// configure Terms and Conditions required action as enabled and default
|
||||||
|
RequiredActionProviderRepresentation tacRep = testRealm().flows().getRequiredAction(UserModel.RequiredAction.TERMS_AND_CONDITIONS.name());
|
||||||
|
Assert.assertNotNull(tacRep);
|
||||||
|
tacRep.setEnabled(true);
|
||||||
|
tacRep.setDefaultAction(true);
|
||||||
|
testRealm().flows().updateRequiredAction(UserModel.RequiredAction.TERMS_AND_CONDITIONS.name(), tacRep);
|
||||||
|
|
||||||
|
try {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.clickRegister();
|
||||||
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
registerPage.register("firstName", "lastName", "registerUserSuccessTermsAcceptance2@email",
|
||||||
|
"registerUserSuccessTermsAcceptance2", "password", "password", null, true, null);
|
||||||
|
|
||||||
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
String userId = events.expectRegister("registerUserSuccessTermsAcceptance2", "registerUserSuccessTermsAcceptance2@email")
|
||||||
|
.assertEvent().getUserId();
|
||||||
|
UserRepresentation user = assertUserRegistered(userId, "registerUserSuccessTermsAcceptance2", "registerUserSuccessTermsAcceptance2@email");
|
||||||
|
Assert.assertNotNull(user.getAttributes());
|
||||||
|
Assert.assertNotNull(user.getAttributes().get(TermsAndConditions.USER_ATTRIBUTE));
|
||||||
|
Assert.assertEquals(1, user.getAttributes().get(TermsAndConditions.USER_ATTRIBUTE).size());
|
||||||
|
Assert.assertTrue(Integer.parseInt(user.getAttributes().get(TermsAndConditions.USER_ATTRIBUTE).get(0)) >= currentTime);
|
||||||
|
} finally {
|
||||||
|
tacRep.setEnabled(false);
|
||||||
|
tacRep.setDefaultAction(false);
|
||||||
|
testRealm().flows().updateRequiredAction(UserModel.RequiredAction.TERMS_AND_CONDITIONS.name(), tacRep);
|
||||||
|
configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegisterShouldFailBeforeUserCreationWhenUserIsInContext() {
|
public void testRegisterShouldFailBeforeUserCreationWhenUserIsInContext() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
Loading…
Reference in a new issue