have a factory like approach for profile contexts

This commit is contained in:
Markus Till 2020-10-01 23:50:25 +02:00 committed by Pedro Igor
parent 21cfa54d4d
commit 802a670cc5
13 changed files with 61 additions and 64 deletions

View file

@ -26,6 +26,5 @@ public interface UserProfileContext {
boolean isCreate();
UserUpdateEvent getUpdateEvent();
UserProfile getCurrent();
UserProfile getUpdated();
UserProfile getCurrentProfile();
}

View file

@ -25,6 +25,6 @@ import org.keycloak.userprofile.validation.UserProfileValidationResult;
*/
public interface UserProfileProvider extends Provider {
UserProfileValidationResult validate(UserProfileContext updateContext);
UserProfileValidationResult validate(UserProfileContext updateContext, UserProfile updatedProfile);
}

View file

@ -37,9 +37,8 @@ import org.keycloak.services.resources.AttributeFormDataProcessor;
import org.keycloak.services.validation.Validation;
import org.keycloak.userprofile.LegacyUserProfileProviderFactory;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.profile.representations.AttributeUserProfile;
import org.keycloak.userprofile.profile.DefaultUserProfileContext;
import org.keycloak.userprofile.profile.representations.IdpUserProfile;
import org.keycloak.userprofile.profile.representations.AttributeUserProfile;
import org.keycloak.userprofile.utils.UserProfileUpdateHelper;
import org.keycloak.userprofile.validation.UserProfileValidationResult;
import org.keycloak.userprofile.validation.UserUpdateEvent;
@ -112,9 +111,7 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
String oldEmail = userCtx.getEmail();
String newEmail = updatedProfile.getFirstAttribute(UserModel.EMAIL);
DefaultUserProfileContext updateContext =
new DefaultUserProfileContext(UserUpdateEvent.IdpReview, new IdpUserProfile(userCtx), updatedProfile);
UserProfileValidationResult result = profileProvider.validate(updateContext);
UserProfileValidationResult result = profileProvider.validate(DefaultUserProfileContext.forIdpReview(userCtx), updatedProfile);
List<FormMessage> errors = Validation.getFormErrorsFromValidation(result);
if (errors != null && !errors.isEmpty()) {

View file

@ -72,9 +72,8 @@ public class RegistrationProfile implements FormAction, FormActionFactory {
UserProfileProvider userProfile = context.getSession().getProvider(UserProfileProvider.class, LegacyUserProfileProviderFactory.PROVIDER_ID);
context.getEvent().detail(Details.REGISTER_METHOD, "form");
DefaultUserProfileContext updateContext = new DefaultUserProfileContext(UserUpdateEvent.RegistrationProfile, updatedProfile);
UserProfileValidationResult result = userProfile.validate(updateContext);
UserProfileValidationResult result = userProfile.validate(DefaultUserProfileContext.forRegistrationProfile(), updatedProfile);
List<FormMessage> errors = Validation.getFormErrorsFromValidation(result);
if (errors.size() > 0) {
@ -98,10 +97,7 @@ public class RegistrationProfile implements FormAction, FormActionFactory {
public void success(FormContext context) {
UserModel user = context.getUser();
AttributeUserProfile updatedProfile = AttributeFormDataProcessor.toUserProfile(context.getHttpRequest().getDecodedFormParameters());
DefaultUserProfileContext updateContext =
new DefaultUserProfileContext(UserUpdateEvent.RegistrationProfile, new UserModelUserProfile(user), updatedProfile);
UserProfileUpdateHelper.update(updateContext.getUpdateEvent(), context.getSession(), user, updatedProfile, false);
UserProfileUpdateHelper.update(UserUpdateEvent.RegistrationProfile, context.getSession(), user, updatedProfile, false);
}
@Override

View file

@ -82,8 +82,7 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
UserProfileProvider profileProvider = context.getSession().getProvider(UserProfileProvider.class, LegacyUserProfileProviderFactory.PROVIDER_ID);
context.getEvent().detail(Details.REGISTER_METHOD, "form");
DefaultUserProfileContext updateContext = new DefaultUserProfileContext(UserUpdateEvent.RegistrationUserCreation, newProfile);
UserProfileValidationResult result = profileProvider.validate(updateContext);
UserProfileValidationResult result = profileProvider.validate(DefaultUserProfileContext.forRegistrationUserCreation(), newProfile);
List<FormMessage> errors = Validation.getFormErrorsFromValidation(result);
if (context.getRealm().isRegistrationEmailAsUsername()) {
@ -128,10 +127,7 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
UserModel user = context.getSession().users().addUser(context.getRealm(), username);
user.setEnabled(true);
DefaultUserProfileContext updateContext =
new DefaultUserProfileContext(UserUpdateEvent.RegistrationUserCreation, new UserModelUserProfile(user), updatedProfile);
UserProfileUpdateHelper.update(updateContext.getUpdateEvent(), context.getSession(), user, updatedProfile, false);
UserProfileUpdateHelper.update(UserUpdateEvent.RegistrationUserCreation, context.getSession(), user, updatedProfile, false);
context.getAuthenticationSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);

View file

@ -80,9 +80,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
String newEmail = updatedProfile.getFirstAttribute(UserModel.EMAIL);
UserProfileProvider userProfile = context.getSession().getProvider(UserProfileProvider.class, LegacyUserProfileProviderFactory.PROVIDER_ID);
DefaultUserProfileContext updateContext =
new DefaultUserProfileContext(UserUpdateEvent.UpdateProfile, new UserModelUserProfile(user), updatedProfile);
UserProfileValidationResult result = userProfile.validate(updateContext);
UserProfileValidationResult result = userProfile.validate(DefaultUserProfileContext.forUpdateProfile(user),updatedProfile);
List<FormMessage> errors = Validation.getFormErrorsFromValidation(result);
if (errors != null && !errors.isEmpty()) {

View file

@ -369,10 +369,8 @@ public class AccountFormService extends AbstractSecuredLocalService {
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class, LegacyUserProfileProviderFactory.PROVIDER_ID);
DefaultUserProfileContext updateContext =
new DefaultUserProfileContext(UserUpdateEvent.Account, new UserModelUserProfile(user), updatedProfile);
UserProfileValidationResult result = profileProvider.validate(updateContext);
UserProfileValidationResult result = profileProvider.validate(DefaultUserProfileContext.forAccountService(user), updatedProfile);
List<FormMessage> errors = Validation.getFormErrorsFromValidation(result);
if (!errors.isEmpty()) {

View file

@ -48,11 +48,11 @@ import org.keycloak.services.util.ResolveRelative;
import org.keycloak.storage.ReadOnlyException;
import org.keycloak.theme.Theme;
import org.keycloak.userprofile.LegacyUserProfileProviderFactory;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.utils.UserProfileUpdateHelper;
import org.keycloak.userprofile.profile.representations.AccountUserRepresentationUserProfile;
import org.keycloak.userprofile.profile.DefaultUserProfileContext;
import org.keycloak.userprofile.profile.representations.UserModelUserProfile;
import org.keycloak.userprofile.validation.UserProfileValidationResult;
import org.keycloak.userprofile.validation.UserUpdateEvent;
@ -175,11 +175,9 @@ public class AccountRestService {
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
UserProfile updatedUser = new AccountUserRepresentationUserProfile(rep);
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class, LegacyUserProfileProviderFactory.PROVIDER_ID);
AccountUserRepresentationUserProfile updatedUser = new AccountUserRepresentationUserProfile(rep);
DefaultUserProfileContext updateContext =
new DefaultUserProfileContext(UserUpdateEvent.Account, new UserModelUserProfile(user), updatedUser);
UserProfileValidationResult result = profileProvider.validate(updateContext);
UserProfileValidationResult result = profileProvider.validate(DefaultUserProfileContext.forAccountService(user), updatedUser);
if (result.hasFailureOfErrorType(Messages.READ_ONLY_USERNAME))
return ErrorResponse.error(Messages.READ_ONLY_USERNAME, Response.Status.BAD_REQUEST);

View file

@ -44,7 +44,7 @@ public class LegacyUserProfileProvider implements UserProfileProvider {
}
@Override
public UserProfileValidationResult validate(UserProfileContext updateContext) {
public UserProfileValidationResult validate(UserProfileContext updateContext, UserProfile updatedProfile) {
RealmModel realm = this.session.getContext().getRealm();
ValidationChainBuilder builder = ValidationChainBuilder.builder();
@ -64,7 +64,7 @@ public class LegacyUserProfileProvider implements UserProfileProvider {
addUserCreationValidators(builder);
break;
}
return new UserProfileValidationResult(builder.build().validate(updateContext));
return new UserProfileValidationResult(builder.build().validate(updateContext,updatedProfile));
}
private void addUserCreationValidators(ValidationChainBuilder builder) {

View file

@ -17,8 +17,14 @@
package org.keycloak.userprofile.profile;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.profile.representations.IdpUserProfile;
import org.keycloak.userprofile.profile.representations.UserModelUserProfile;
import org.keycloak.userprofile.profile.representations.UserRepresentationUserProfile;
import org.keycloak.userprofile.validation.UserUpdateEvent;
/**
@ -27,23 +33,37 @@ import org.keycloak.userprofile.validation.UserUpdateEvent;
public class DefaultUserProfileContext implements UserProfileContext {
private boolean isCreated;
private UserProfile currentUserProfile;
private UserProfile updatedUserProfile;
private UserUpdateEvent userUpdateEvent;
public DefaultUserProfileContext(UserUpdateEvent userUpdateEvent, UserProfile updatedUserProfile) {
private DefaultUserProfileContext(UserUpdateEvent userUpdateEvent, UserProfile currentUserProfile) {
this.userUpdateEvent = userUpdateEvent;
this.isCreated = false;
this.currentUserProfile = null;
this.updatedUserProfile = updatedUserProfile;
}
public DefaultUserProfileContext(UserUpdateEvent userUpdateEvent, UserProfile currentUserProfile, UserProfile updatedUserProfile) {
this.userUpdateEvent = userUpdateEvent;
this.isCreated = true;
this.currentUserProfile = currentUserProfile;
this.updatedUserProfile = updatedUserProfile;
}
public static DefaultUserProfileContext forIdpReview(SerializedBrokeredIdentityContext currentUser) {
return new DefaultUserProfileContext(UserUpdateEvent.IdpReview, new IdpUserProfile(currentUser));
}
public static DefaultUserProfileContext forUpdateProfile(UserModel currentUser) {
return new DefaultUserProfileContext(UserUpdateEvent.UpdateProfile, new UserModelUserProfile(currentUser));
}
public static DefaultUserProfileContext forAccountService(UserModel currentUser) {
return new DefaultUserProfileContext(UserUpdateEvent.Account, new UserModelUserProfile(currentUser));
}
public static DefaultUserProfileContext forRegistrationUserCreation() {
return new DefaultUserProfileContext(UserUpdateEvent.RegistrationUserCreation, null);
}
public static DefaultUserProfileContext forRegistrationProfile() {
return new DefaultUserProfileContext(UserUpdateEvent.RegistrationProfile, null);
}
public static DefaultUserProfileContext forUserResource(UserRepresentation rep) {
return new DefaultUserProfileContext(UserUpdateEvent.UserResource, new UserRepresentationUserProfile(rep));
}
@Override
public boolean isCreate() {
@ -51,15 +71,10 @@ public class DefaultUserProfileContext implements UserProfileContext {
}
@Override
public UserProfile getCurrent() {
public UserProfile getCurrentProfile() {
return currentUserProfile;
}
@Override
public UserProfile getUpdated() {
return updatedUserProfile;
}
@Override
public UserUpdateEvent getUpdateEvent(){
return userUpdateEvent;

View file

@ -41,16 +41,16 @@ public class StaticValidators {
public static BiFunction<String, UserProfileContext, Boolean> userNameExists(KeycloakSession session) {
return (value, context) ->
!(context.getCurrent() != null
&& !value.equals(context.getCurrent().getFirstAttribute(UserModel.USERNAME))
!(context.getCurrentProfile() != null
&& !value.equals(context.getCurrentProfile().getFirstAttribute(UserModel.USERNAME))
&& session.users().getUserByUsername(value, session.getContext().getRealm()) != null);
}
public static BiFunction<String, UserProfileContext, Boolean> isUserMutable(RealmModel realm) {
return (value, context) ->
!(!realm.isEditUsernameAllowed()
&& context.getCurrent() != null
&& !value.equals(context.getCurrent().getFirstAttribute(UserModel.USERNAME))
&& context.getCurrentProfile() != null
&& !value.equals(context.getCurrentProfile().getFirstAttribute(UserModel.USERNAME))
);
}
@ -65,7 +65,7 @@ public class StaticValidators {
RealmModel realm = session.getContext().getRealm();
if (!realm.isDuplicateEmailsAllowed()) {
UserModel userByEmail = session.users().getUserByEmail(value, realm);
return !(realm.isRegistrationEmailAsUsername() && userByEmail != null && context.getCurrent() != null && !userByEmail.getId().equals(context.getCurrent().getId()));
return !(realm.isRegistrationEmailAsUsername() && userByEmail != null && context.getCurrentProfile() != null && !userByEmail.getId().equals(context.getCurrentProfile().getId()));
}
return true;
};
@ -77,7 +77,7 @@ public class StaticValidators {
if (!realm.isDuplicateEmailsAllowed()) {
UserModel userByEmail = session.users().getUserByEmail(value, realm);
// check for duplicated email
return !(userByEmail != null && (context.getCurrent() == null || !userByEmail.getId().equals(context.getCurrent().getId())));
return !(userByEmail != null && (context.getCurrentProfile() == null || !userByEmail.getId().equals(context.getCurrentProfile().getId())));
}
return true;
};

View file

@ -17,6 +17,7 @@
package org.keycloak.userprofile.validation;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import java.util.ArrayList;
@ -32,17 +33,17 @@ public class ValidationChain {
this.attributeValidators = attributeValidators;
}
public List<AttributeValidationResult> validate(UserProfileContext updateContext) {
public List<AttributeValidationResult> validate(UserProfileContext updateContext, UserProfile updatedProfile) {
List<AttributeValidationResult> overallResults = new ArrayList<>();
for (AttributeValidator attribute : attributeValidators) {
List<ValidationResult> validationResults = new ArrayList<>();
String attributeKey = attribute.attributeKey;
String attributeValue = updateContext.getUpdated().getFirstAttribute(attributeKey);
String attributeValue = updatedProfile.getFirstAttribute(attributeKey);
boolean attributeChanged = false;
if (attributeValue != null) {
attributeChanged = updateContext.getCurrent() != null && !attributeValue.equals(updateContext.getCurrent().getFirstAttribute(attributeKey));
attributeChanged = updateContext.getCurrentProfile() != null && !attributeValue.equals(updateContext.getCurrentProfile().getFirstAttribute(attributeKey));
for (Validator validator : attribute.validators) {
validationResults.add(new ValidationResult(validator.function.apply(attributeValue, updateContext), validator.errorType));
}

View file

@ -18,6 +18,7 @@ public class ValidationChainTest {
ValidationChain testchain;
UserProfile user;
DefaultUserProfileContext updateContext;
UserRepresentation rep = new UserRepresentation();
@Before
public void setUp() throws Exception {
@ -27,7 +28,6 @@ public class ValidationChainTest {
.addAttributeValidator().forAttribute("firstName")
.addValidationFunction("FIRST_NAME_FIELD_ERRORKEY", (value, updateUserProfileContext) -> true).build();
UserRepresentation rep = new UserRepresentation();
//default user content
rep.singleAttribute(UserModel.FIRST_NAME, "firstName");
rep.singleAttribute(UserModel.LAST_NAME, "lastName");
@ -35,15 +35,14 @@ public class ValidationChainTest {
rep.singleAttribute("FAKE_FIELD", "content");
rep.singleAttribute("NULLABLE_FIELD", null);
user = new UserRepresentationUserProfile(rep);
updateContext = new DefaultUserProfileContext(UserUpdateEvent.Account,null, user);
updateContext = DefaultUserProfileContext.forRegistrationProfile();
}
@Test
public void validate() {
testchain = builder.build();
UserProfileValidationResult results = new UserProfileValidationResult(testchain.validate(updateContext));
UserProfileValidationResult results = new UserProfileValidationResult(testchain.validate(updateContext, new UserRepresentationUserProfile(rep)));
Assert.assertEquals(true, results.hasFailureOfErrorType("FAKE_FIELD_ERRORKEY"));
Assert.assertEquals(false, results.hasFailureOfErrorType("FIRST_NAME_FIELD_ERRORKEY"));
Assert.assertEquals(true, results.getValidationResults().stream().filter(o -> o.getField().equals("firstName")).collect(Collectors.toList()).get(0).isValid());
@ -58,7 +57,7 @@ public class ValidationChainTest {
.addAttributeValidator().forAttribute("FAKE_FIELD")
.addValidationFunction("FAKE_FIELD_ERRORKEY_2", (value, updateUserProfileContext) -> false).build().build();
UserProfileValidationResult results = new UserProfileValidationResult(testchain.validate(updateContext));
UserProfileValidationResult results = new UserProfileValidationResult(testchain.validate(updateContext, new UserRepresentationUserProfile(rep)));
Assert.assertEquals(true, results.hasFailureOfErrorType("FAKE_FIELD_ERRORKEY_1"));
Assert.assertEquals(true, results.hasFailureOfErrorType("FAKE_FIELD_ERRORKEY_2"));
Assert.assertEquals(true, results.getValidationResults().stream().filter(o -> o.getField().equals("firstName")).collect(Collectors.toList()).get(0).isValid());
@ -68,7 +67,7 @@ public class ValidationChainTest {
@Test
public void emptyChain() {
UserProfileValidationResult results = new UserProfileValidationResult(ValidationChainBuilder.builder().build().validate(updateContext));
UserProfileValidationResult results = new UserProfileValidationResult(ValidationChainBuilder.builder().build().validate(updateContext,new UserRepresentationUserProfile(rep) ));
Assert.assertEquals(Collections.emptyList(), results.getValidationResults());
}
}