KEYCLOAK-18700 - consistently record User profile attribute changes in
UPDATE_PROFILE event
This commit is contained in:
parent
4fe7d6d318
commit
2be5f528e4
18 changed files with 451 additions and 71 deletions
|
@ -21,10 +21,14 @@ package org.keycloak.events;
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface Details {
|
public interface Details {
|
||||||
|
String PREF_PREVIOUS = "previous_";
|
||||||
|
String PREF_UPDATED = "updated_";
|
||||||
|
|
||||||
String CUSTOM_REQUIRED_ACTION="custom_required_action";
|
String CUSTOM_REQUIRED_ACTION="custom_required_action";
|
||||||
|
String CONTEXT = "context";
|
||||||
String EMAIL = "email";
|
String EMAIL = "email";
|
||||||
String PREVIOUS_EMAIL = "previous_email";
|
String PREVIOUS_EMAIL = PREF_PREVIOUS + "email";
|
||||||
String UPDATED_EMAIL = "updated_email";
|
String UPDATED_EMAIL = PREF_UPDATED + "email";
|
||||||
String ACTION = "action";
|
String ACTION = "action";
|
||||||
String CODE_ID = "code_id";
|
String CODE_ID = "code_id";
|
||||||
String REDIRECT_URI = "redirect_uri";
|
String REDIRECT_URI = "redirect_uri";
|
||||||
|
@ -39,10 +43,10 @@ public interface Details {
|
||||||
String USERNAME = "username";
|
String USERNAME = "username";
|
||||||
String FIRST_NAME = "first_name";
|
String FIRST_NAME = "first_name";
|
||||||
String LAST_NAME = "last_name";
|
String LAST_NAME = "last_name";
|
||||||
String PREVIOUS_FIRST_NAME = "previous_first_name";
|
String PREVIOUS_FIRST_NAME = PREF_PREVIOUS + "first_name";
|
||||||
String UPDATED_FIRST_NAME = "updated_first_name";
|
String UPDATED_FIRST_NAME = PREF_UPDATED + "first_name";
|
||||||
String PREVIOUS_LAST_NAME = "previous_last_name";
|
String PREVIOUS_LAST_NAME = PREF_PREVIOUS + "last_name";
|
||||||
String UPDATED_LAST_NAME = "updated_last_name";
|
String UPDATED_LAST_NAME = PREF_UPDATED + "last_name";
|
||||||
String REMEMBER_ME = "remember_me";
|
String REMEMBER_ME = "remember_me";
|
||||||
String TOKEN_ID = "token_id";
|
String TOKEN_ID = "token_id";
|
||||||
String REFRESH_TOKEN_ID = "refresh_token_id";
|
String REFRESH_TOKEN_ID = "refresh_token_id";
|
||||||
|
|
|
@ -26,12 +26,14 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -145,6 +147,35 @@ public class EventBuilder {
|
||||||
event.getDetails().put(key, value);
|
event.getDetails().put(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add event detail where strings from the input Collection are filtered not to contain <code>null</code> and then joined using <code>::</code> character.
|
||||||
|
*
|
||||||
|
* @param key of the detail
|
||||||
|
* @param value, can be null
|
||||||
|
* @return builder for chaining
|
||||||
|
*/
|
||||||
|
public EventBuilder detail(String key, Collection<String> values) {
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return detail(key, values.stream().filter(Objects::nonNull).collect(Collectors.joining("::")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add event detail where strings from the input Stream are filtered not to contain <code>null</code> and then joined using <code>::</code> character.
|
||||||
|
*
|
||||||
|
* @param key of the detail
|
||||||
|
* @param value, can be null
|
||||||
|
* @return builder for chaining
|
||||||
|
*/
|
||||||
|
public EventBuilder detail(String key, Stream<String> values) {
|
||||||
|
if (values == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return detail(key, values.filter(Objects::nonNull).collect(Collectors.joining("::")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public EventBuilder removeDetail(String key) {
|
public EventBuilder removeDetail(String key) {
|
||||||
if (event.getDetails() != null) {
|
if (event.getDetails() != null) {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.userprofile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of the user profile attribute change listener.
|
||||||
|
*
|
||||||
|
* @author Vlastimil Elias <velias@redhat.com>
|
||||||
|
*
|
||||||
|
* @see UserProfile#update(boolean, AttributeChangeListener...)
|
||||||
|
* @see UserProfile#update(AttributeChangeListener...)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface AttributeChangeListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called for each user attribute change.
|
||||||
|
*
|
||||||
|
* @param name of the changed user attribute
|
||||||
|
* @param user model where new attribute value is applied already (can be null if attribute is removed)
|
||||||
|
* @param oldValue of the attribute before the change (can be null)
|
||||||
|
*/
|
||||||
|
void onChange(String name, UserModel user, List<String> oldValue);
|
||||||
|
|
||||||
|
}
|
|
@ -24,7 +24,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -89,7 +88,7 @@ public final class DefaultUserProfile implements UserProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(boolean removeAttributes, BiConsumer<String, UserModel>... changeListener) {
|
public void update(boolean removeAttributes, AttributeChangeListener... changeListener) {
|
||||||
if (!validated) {
|
if (!validated) {
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
@ -97,7 +96,7 @@ public final class DefaultUserProfile implements UserProfile {
|
||||||
updateInternal(user, removeAttributes, changeListener);
|
updateInternal(user, removeAttributes, changeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserModel updateInternal(UserModel user, boolean removeAttributes, BiConsumer<String, UserModel>... changeListener) {
|
private UserModel updateInternal(UserModel user, boolean removeAttributes, AttributeChangeListener... changeListener) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new RuntimeException("No user model provided for persisting changes");
|
throw new RuntimeException("No user model provided for persisting changes");
|
||||||
}
|
}
|
||||||
|
@ -120,8 +119,8 @@ public final class DefaultUserProfile implements UserProfile {
|
||||||
user.setEmailVerified(false);
|
user.setEmailVerified(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (BiConsumer<String, UserModel> listener : changeListener) {
|
for (AttributeChangeListener listener : changeListener) {
|
||||||
listener.accept(name, user);
|
listener.onChange(name, user, currentValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +137,13 @@ public final class DefaultUserProfile implements UserProfile {
|
||||||
if (this.attributes.isReadOnly(attr)) {
|
if (this.attributes.isReadOnly(attr)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> currentValue = user.getAttributeStream(attr).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
user.removeAttribute(attr);
|
user.removeAttribute(attr);
|
||||||
|
|
||||||
|
for (AttributeChangeListener listener : changeListener) {
|
||||||
|
listener.onChange(attr, user, currentValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ModelException me) {
|
} catch (ModelException me) {
|
||||||
|
|
|
@ -66,7 +66,7 @@ public interface UserProfile {
|
||||||
* @param changeListener a set of one or more listeners to listen for attribute changes
|
* @param changeListener a set of one or more listeners to listen for attribute changes
|
||||||
* @throws ValidationException in case of any validation error
|
* @throws ValidationException in case of any validation error
|
||||||
*/
|
*/
|
||||||
void update(boolean removeAttributes, BiConsumer<String, UserModel>... changeListener) throws ValidationException;
|
void update(boolean removeAttributes, AttributeChangeListener... changeListener) throws ValidationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>The same as {@link #update(boolean, BiConsumer[])} but forcing the removal of attributes.
|
* <p>The same as {@link #update(boolean, BiConsumer[])} but forcing the removal of attributes.
|
||||||
|
@ -74,7 +74,7 @@ public interface UserProfile {
|
||||||
* @param changeListener a set of one or more listeners to listen for attribute changes
|
* @param changeListener a set of one or more listeners to listen for attribute changes
|
||||||
* @throws ValidationException in case of any validation error
|
* @throws ValidationException in case of any validation error
|
||||||
*/
|
*/
|
||||||
default void update(BiConsumer<String, UserModel>... changeListener) throws ValidationException, RuntimeException {
|
default void update(AttributeChangeListener... changeListener) throws ValidationException, RuntimeException {
|
||||||
update(true, changeListener);
|
update(true, changeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,8 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
|
||||||
@Override
|
@Override
|
||||||
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
|
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
|
||||||
EventBuilder event = context.getEvent();
|
EventBuilder event = context.getEvent();
|
||||||
event.event(EventType.UPDATE_PROFILE);
|
//velias: looks like UPDATE_PROFILE event is not fired. IMHO it should not be fired here as user record in keycloak is not changed, user doesn't exist yet
|
||||||
|
event.event(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name());
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
UserModelDelegate updatedProfile = new UserModelDelegate(null) {
|
UserModelDelegate updatedProfile = new UserModelDelegate(null) {
|
||||||
|
|
||||||
|
@ -153,10 +154,10 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
|
||||||
try {
|
try {
|
||||||
String oldEmail = userCtx.getEmail();
|
String oldEmail = userCtx.getEmail();
|
||||||
|
|
||||||
profile.update((attributeName, userModel) -> {
|
profile.update((attributeName, userModel, oldValue) -> {
|
||||||
if (attributeName.equals(UserModel.EMAIL)) {
|
if (attributeName.equals(UserModel.EMAIL)) {
|
||||||
context.getAuthenticationSession().setAuthNote(UPDATE_PROFILE_EMAIL_CHANGED, "true");
|
context.getAuthenticationSession().setAuthNote(UPDATE_PROFILE_EMAIL_CHANGED, "true");
|
||||||
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, profile.getAttributes().getFirstValue(UserModel.EMAIL)).success();
|
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name()).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, profile.getAttributes().getFirstValue(UserModel.EMAIL)).success();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (ValidationException pve) {
|
} catch (ValidationException pve) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.keycloak.userprofile.ValidationException;
|
import org.keycloak.userprofile.ValidationException;
|
||||||
import org.keycloak.userprofile.UserProfile;
|
import org.keycloak.userprofile.UserProfile;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
|
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -64,29 +65,16 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
|
||||||
@Override
|
@Override
|
||||||
public void processAction(RequiredActionContext context) {
|
public void processAction(RequiredActionContext context) {
|
||||||
EventBuilder event = context.getEvent();
|
EventBuilder event = context.getEvent();
|
||||||
event.event(EventType.UPDATE_PROFILE);
|
event.event(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name());
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
UserModel user = context.getUser();
|
UserModel user = context.getUser();
|
||||||
|
|
||||||
String oldFirstName = user.getFirstName();
|
|
||||||
String oldLastName = user.getLastName();
|
|
||||||
String oldEmail = user.getEmail();
|
|
||||||
UserProfileProvider provider = context.getSession().getProvider(UserProfileProvider.class);
|
UserProfileProvider provider = context.getSession().getProvider(UserProfileProvider.class);
|
||||||
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, formData, user);
|
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, formData, user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// backward compatibility with old account console where attributes are not removed if missing
|
// backward compatibility with old account console where attributes are not removed if missing
|
||||||
profile.update(false, (attributeName, userModel) -> {
|
profile.update(false, new EventAuditingAttributeChangeListener(profile, event));
|
||||||
if (attributeName.equals(UserModel.FIRST_NAME)) {
|
|
||||||
event.detail(Details.PREVIOUS_FIRST_NAME, oldFirstName).detail(Details.UPDATED_FIRST_NAME, user.getFirstName());
|
|
||||||
}
|
|
||||||
if (attributeName.equals(UserModel.LAST_NAME)) {
|
|
||||||
event.detail(Details.PREVIOUS_LAST_NAME, oldLastName).detail(Details.UPDATED_LAST_NAME, user.getLastName());
|
|
||||||
}
|
|
||||||
if (attributeName.equals(UserModel.EMAIL)) {
|
|
||||||
event.detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, user.getEmail());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.success();
|
context.success();
|
||||||
} catch (ValidationException pve) {
|
} catch (ValidationException pve) {
|
||||||
|
@ -95,7 +83,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
|
||||||
context.challenge(createResponse(context, formData, errors));
|
context.challenge(createResponse(context, formData, errors));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserModel.RequiredAction getResponseAction(){
|
protected UserModel.RequiredAction getResponseAction(){
|
||||||
return UserModel.RequiredAction.UPDATE_PROFILE;
|
return UserModel.RequiredAction.UPDATE_PROFILE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.keycloak.userprofile.ValidationException;
|
import org.keycloak.userprofile.ValidationException;
|
||||||
import org.keycloak.userprofile.UserProfile;
|
import org.keycloak.userprofile.UserProfile;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
|
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.utils.CredentialHelper;
|
import org.keycloak.utils.CredentialHelper;
|
||||||
|
|
||||||
|
@ -364,28 +365,14 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
String oldFirstName = user.getFirstName();
|
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).detail(Details.CONTEXT, UserProfileContext.ACCOUNT_OLD.name());
|
||||||
String oldLastName = user.getLastName();
|
|
||||||
String oldEmail = user.getEmail();
|
|
||||||
|
|
||||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
|
|
||||||
|
|
||||||
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
||||||
UserProfile profile = profileProvider.create(UserProfileContext.ACCOUNT_OLD, formData, user);
|
UserProfile profile = profileProvider.create(UserProfileContext.ACCOUNT_OLD, formData, user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// backward compatibility with old account console where attributes are not removed if missing
|
// backward compatibility with old account console where attributes are not removed if missing
|
||||||
profile.update(false, (attributeName, userModel) -> {
|
profile.update(false, new EventAuditingAttributeChangeListener(profile, event));
|
||||||
if (attributeName.equals(UserModel.EMAIL)) {
|
|
||||||
event.detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, user.getEmail()).success();
|
|
||||||
}
|
|
||||||
if (attributeName.equals(UserModel.FIRST_NAME)) {
|
|
||||||
event.detail(Details.PREVIOUS_FIRST_NAME, oldFirstName).detail(Details.UPDATED_FIRST_NAME, user.getFirstName());
|
|
||||||
}
|
|
||||||
if (attributeName.equals(UserModel.LAST_NAME)) {
|
|
||||||
event.detail(Details.PREVIOUS_LAST_NAME, oldLastName).detail(Details.UPDATED_LAST_NAME, user.getLastName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (ValidationException pve) {
|
} catch (ValidationException pve) {
|
||||||
List<FormMessage> errors = Validation.getFormErrorsFromValidation(pve.getErrors());
|
List<FormMessage> errors = Validation.getFormErrorsFromValidation(pve.getErrors());
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.enums.AccountRestApiVersion;
|
import org.keycloak.common.enums.AccountRestApiVersion;
|
||||||
import org.keycloak.common.util.StringPropertyReplacer;
|
import org.keycloak.common.util.StringPropertyReplacer;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventStoreProvider;
|
import org.keycloak.events.EventStoreProvider;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
@ -85,6 +86,7 @@ import org.keycloak.userprofile.Attributes;
|
||||||
import org.keycloak.userprofile.UserProfile;
|
import org.keycloak.userprofile.UserProfile;
|
||||||
import org.keycloak.userprofile.UserProfileContext;
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
|
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||||
import org.keycloak.userprofile.ValidationException;
|
import org.keycloak.userprofile.ValidationException;
|
||||||
import org.keycloak.userprofile.ValidationException.Error;
|
import org.keycloak.userprofile.ValidationException.Error;
|
||||||
import org.keycloak.validate.Validators;
|
import org.keycloak.validate.Validators;
|
||||||
|
@ -199,14 +201,14 @@ public class AccountRestService {
|
||||||
public Response updateAccount(UserRepresentation rep) {
|
public Response updateAccount(UserRepresentation rep) {
|
||||||
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
|
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).detail(Details.CONTEXT, UserProfileContext.ACCOUNT.name());
|
||||||
|
|
||||||
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
|
||||||
UserProfile profile = profileProvider.create(UserProfileContext.ACCOUNT, rep.toAttributes(), auth.getUser());
|
UserProfile profile = profileProvider.create(UserProfileContext.ACCOUNT, rep.toAttributes(), auth.getUser());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
profile.update();
|
profile.update(new EventAuditingAttributeChangeListener(profile, event));
|
||||||
|
|
||||||
event.success();
|
event.success();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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.userprofile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link AttributeChangeListener} to audit user profile attribute changes into {@link Event}.
|
||||||
|
*
|
||||||
|
* Adds info about user profile attribute change into {@link Event}'s detail field.
|
||||||
|
*
|
||||||
|
* @author Vlastimil Elias <velias@redhat.com>
|
||||||
|
*
|
||||||
|
* @see UserProfile#update(AttributeChangeListener...)
|
||||||
|
*/
|
||||||
|
public class EventAuditingAttributeChangeListener implements AttributeChangeListener {
|
||||||
|
|
||||||
|
private EventBuilder event;
|
||||||
|
private UserProfile profile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param profile used to read attribute configuration from
|
||||||
|
* @param event to add detail info into
|
||||||
|
*/
|
||||||
|
public EventAuditingAttributeChangeListener(UserProfile profile, EventBuilder event) {
|
||||||
|
super();
|
||||||
|
this.profile = profile;
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(String attributeName, UserModel userModel, List<String> oldValue) {
|
||||||
|
if (attributeName.equals(UserModel.FIRST_NAME)) {
|
||||||
|
event.detail(Details.PREVIOUS_FIRST_NAME, oldValue).detail(Details.UPDATED_FIRST_NAME, userModel.getFirstName());
|
||||||
|
} else if (attributeName.equals(UserModel.LAST_NAME)) {
|
||||||
|
event.detail(Details.PREVIOUS_LAST_NAME, oldValue).detail(Details.UPDATED_LAST_NAME, userModel.getLastName());
|
||||||
|
} else if (attributeName.equals(UserModel.EMAIL)) {
|
||||||
|
event.detail(Details.PREVIOUS_EMAIL, oldValue).detail(Details.UPDATED_EMAIL, userModel.getEmail());
|
||||||
|
} else {
|
||||||
|
event.detail(Details.PREF_PREVIOUS + attributeName, oldValue).detail(Details.PREF_UPDATED + attributeName, userModel.getAttributeStream(attributeName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -70,6 +70,8 @@ import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UIUtils;
|
import org.keycloak.testsuite.util.UIUtils;
|
||||||
import org.keycloak.testsuite.util.URLUtils;
|
import org.keycloak.testsuite.util.URLUtils;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.NoSuchElementException;
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
|
@ -713,7 +715,9 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertEquals("New last", profilePage.getLastName());
|
Assert.assertEquals("New last", profilePage.getLastName());
|
||||||
Assert.assertEquals("new@email.com", profilePage.getEmail());
|
Assert.assertEquals("new@email.com", profilePage.getEmail());
|
||||||
|
|
||||||
events.expectAccount(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
events.expectAccount(EventType.UPDATE_PROFILE)
|
||||||
|
.detail(Details.CONTEXT, UserProfileContext.ACCOUNT_OLD.name())
|
||||||
|
.detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||||
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last")
|
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last")
|
||||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com")
|
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com")
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.testsuite.account;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
@ -30,6 +31,8 @@ import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.enums.AccountRestApiVersion;
|
import org.keycloak.common.enums.AccountRestApiVersion;
|
||||||
import org.keycloak.common.util.ObjectUtil;
|
import org.keycloak.common.util.ObjectUtil;
|
||||||
import org.keycloak.credential.CredentialTypeMetadata;
|
import org.keycloak.credential.CredentialTypeMetadata;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.credential.OTPCredentialModel;
|
import org.keycloak.models.credential.OTPCredentialModel;
|
||||||
|
@ -55,6 +58,7 @@ import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentati
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.account.AccountCredentialResource;
|
import org.keycloak.services.resources.account.AccountCredentialResource;
|
||||||
import org.keycloak.services.util.ResolveRelative;
|
import org.keycloak.services.util.ResolveRelative;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
|
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
|
@ -63,6 +67,7 @@ import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.TokenUtil;
|
import org.keycloak.testsuite.util.TokenUtil;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.keycloak.validate.validators.EmailValidator;
|
import org.keycloak.validate.validators.EmailValidator;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -88,6 +93,9 @@ import static org.junit.Assert.assertTrue;
|
||||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||||
public class AccountRestServiceTest extends AbstractRestServiceTest {
|
public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUserProfileMetadata_EditUsernameAllowed() throws IOException {
|
public void testGetUserProfileMetadata_EditUsernameAllowed() throws IOException {
|
||||||
|
|
||||||
|
@ -249,6 +257,58 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfileEvent() throws IOException {
|
||||||
|
UserRepresentation user = getUser();
|
||||||
|
String originalUsername = user.getUsername();
|
||||||
|
String originalFirstName = user.getFirstName();
|
||||||
|
String originalLastName = user.getLastName();
|
||||||
|
String originalEmail = user.getEmail();
|
||||||
|
Map<String, List<String>> originalAttributes = new HashMap<>(user.getAttributes());
|
||||||
|
|
||||||
|
try {
|
||||||
|
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
|
||||||
|
|
||||||
|
realmRep.setRegistrationEmailAsUsername(false);
|
||||||
|
adminClient.realm("test").update(realmRep);
|
||||||
|
|
||||||
|
user.setEmail("bobby@localhost");
|
||||||
|
user.setFirstName("Homer");
|
||||||
|
user.setLastName("Simpsons");
|
||||||
|
user.getAttributes().put("attr1", Collections.singletonList("val1"));
|
||||||
|
user.getAttributes().put("attr2", Collections.singletonList("val2"));
|
||||||
|
|
||||||
|
user = updateAndGet(user);
|
||||||
|
|
||||||
|
//skip login to the REST API event
|
||||||
|
events.poll();
|
||||||
|
events.expectAccount(EventType.UPDATE_PROFILE).user(user.getId())
|
||||||
|
.detail(Details.CONTEXT, UserProfileContext.ACCOUNT.name())
|
||||||
|
.detail(Details.PREVIOUS_EMAIL, originalEmail)
|
||||||
|
.detail(Details.UPDATED_EMAIL, "bobby@localhost")
|
||||||
|
.detail(Details.PREVIOUS_FIRST_NAME, originalFirstName)
|
||||||
|
.detail(Details.PREVIOUS_LAST_NAME, originalLastName)
|
||||||
|
.detail(Details.UPDATED_FIRST_NAME, "Homer")
|
||||||
|
.detail(Details.UPDATED_LAST_NAME, "Simpsons")
|
||||||
|
.assertEvent();
|
||||||
|
events.assertEmpty();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
|
||||||
|
realmRep.setEditUsernameAllowed(true);
|
||||||
|
adminClient.realm("test").update(realmRep);
|
||||||
|
|
||||||
|
user.setUsername(originalUsername);
|
||||||
|
user.setFirstName(originalFirstName);
|
||||||
|
user.setLastName(originalLastName);
|
||||||
|
user.setEmail(originalEmail);
|
||||||
|
user.setAttributes(originalAttributes);
|
||||||
|
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
|
||||||
|
System.out.println(response.asString());
|
||||||
|
assertEquals(204, response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateProfile() throws IOException {
|
public void testUpdateProfile() throws IOException {
|
||||||
UserRepresentation user = getUser();
|
UserRepresentation user = getUser();
|
||||||
|
@ -278,7 +338,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
assertEquals("val1", user.getAttributes().get("attr1").get(0));
|
assertEquals("val1", user.getAttributes().get("attr1").get(0));
|
||||||
assertEquals(1, user.getAttributes().get("attr2").size());
|
assertEquals(1, user.getAttributes().get("attr2").size());
|
||||||
assertEquals("val2", user.getAttributes().get("attr2").get(0));
|
assertEquals("val2", user.getAttributes().get("attr2").get(0));
|
||||||
|
|
||||||
// Update attributes
|
// Update attributes
|
||||||
user.getAttributes().remove("attr1");
|
user.getAttributes().remove("attr1");
|
||||||
user.getAttributes().get("attr2").add("val3");
|
user.getAttributes().get("attr2").add("val3");
|
||||||
|
|
|
@ -25,17 +25,25 @@ import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_E
|
||||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_ONLY;
|
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_ONLY;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.representations.account.UserProfileAttributeMetadata;
|
import org.keycloak.representations.account.UserProfileAttributeMetadata;
|
||||||
import org.keycloak.representations.account.UserRepresentation;
|
import org.keycloak.representations.account.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
import org.keycloak.testsuite.forms.VerifyProfileTest;
|
import org.keycloak.testsuite.forms.VerifyProfileTest;
|
||||||
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
|
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -164,6 +172,78 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
|
||||||
return uam.getValidators().get(validatorId);
|
return uam.getValidators().get(validatorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfileEventWithAdditionalAttributesAuditing() throws IOException {
|
||||||
|
|
||||||
|
setUserProfileConfiguration("{\"attributes\": ["
|
||||||
|
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||||
|
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||||
|
+ "{\"name\": \"attr1\"," + PERMISSIONS_ALL + "},"
|
||||||
|
+ "{\"name\": \"attr2\"," + PERMISSIONS_ALL + "}"
|
||||||
|
+ "]}");
|
||||||
|
|
||||||
|
UserRepresentation user = getUser();
|
||||||
|
String originalUsername = user.getUsername();
|
||||||
|
String originalFirstName = user.getFirstName();
|
||||||
|
String originalLastName = user.getLastName();
|
||||||
|
String originalEmail = user.getEmail();
|
||||||
|
Map<String, List<String>> originalAttributes = new HashMap<>(user.getAttributes());
|
||||||
|
|
||||||
|
try {
|
||||||
|
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
|
||||||
|
|
||||||
|
realmRep.setRegistrationEmailAsUsername(false);
|
||||||
|
adminClient.realm("test").update(realmRep);
|
||||||
|
|
||||||
|
user.setEmail("bobby@localhost");
|
||||||
|
user.setFirstName("Homer");
|
||||||
|
user.setLastName("Simpsons");
|
||||||
|
user.getAttributes().put("attr1", Collections.singletonList("val1"));
|
||||||
|
user.getAttributes().put("attr2", Collections.singletonList("val2"));
|
||||||
|
|
||||||
|
user = updateAndGet(user);
|
||||||
|
|
||||||
|
//skip login to the REST API event
|
||||||
|
events.poll();
|
||||||
|
events.expectAccount(EventType.UPDATE_PROFILE).user(user.getId())
|
||||||
|
.detail(Details.CONTEXT, UserProfileContext.ACCOUNT.name())
|
||||||
|
.detail(Details.PREVIOUS_EMAIL, originalEmail)
|
||||||
|
.detail(Details.UPDATED_EMAIL, "bobby@localhost")
|
||||||
|
.detail(Details.PREVIOUS_FIRST_NAME, originalFirstName)
|
||||||
|
.detail(Details.PREVIOUS_LAST_NAME, originalLastName)
|
||||||
|
.detail(Details.UPDATED_FIRST_NAME, "Homer")
|
||||||
|
.detail(Details.UPDATED_LAST_NAME, "Simpsons")
|
||||||
|
.detail(Details.PREF_UPDATED+"attr2", "val2")
|
||||||
|
.assertEvent();
|
||||||
|
events.assertEmpty();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
|
||||||
|
realmRep.setEditUsernameAllowed(true);
|
||||||
|
adminClient.realm("test").update(realmRep);
|
||||||
|
|
||||||
|
user.setUsername(originalUsername);
|
||||||
|
user.setFirstName(originalFirstName);
|
||||||
|
user.setLastName(originalLastName);
|
||||||
|
user.setEmail(originalEmail);
|
||||||
|
user.setAttributes(originalAttributes);
|
||||||
|
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
|
||||||
|
System.out.println(response.asString());
|
||||||
|
assertEquals(204, response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfileEvent() throws IOException {
|
||||||
|
setUserProfileConfiguration("{\"attributes\": ["
|
||||||
|
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||||
|
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}},"
|
||||||
|
+ "{\"name\": \"attr1\"," + PERMISSIONS_ALL + "},"
|
||||||
|
+ "{\"name\": \"attr2\"," + PERMISSIONS_ALL + "}"
|
||||||
|
+ "]}");
|
||||||
|
super.testUpdateProfileEvent();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUpdateProfile() throws IOException {
|
public void testUpdateProfile() throws IOException {
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
@ -358,7 +359,7 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
||||||
|
|
||||||
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name()).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.keycloak.testsuite.forms.VerifyProfileTest;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.util.ClientScopeBuilder;
|
import org.keycloak.testsuite.util.ClientScopeBuilder;
|
||||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||||
|
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -418,6 +419,12 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
||||||
//submit OK
|
//submit OK
|
||||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||||
|
|
||||||
|
// we also test additional attribute configured to be audited in the event
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PROFILE)
|
||||||
|
.detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "FirstCC")
|
||||||
|
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "LastCC")
|
||||||
|
.detail(Details.PREF_UPDATED + "department", "DepartmentCC")
|
||||||
|
.assertEvent();
|
||||||
|
|
||||||
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));
|
||||||
|
@ -451,6 +458,12 @@ public class RequiredActionUpdateProfileWithUserProfileTest extends RequiredActi
|
||||||
//submit OK
|
//submit OK
|
||||||
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
updateProfilePage.updateWithDepartment("FirstCC", "LastCC", "DepartmentCC", USERNAME1, USERNAME1);
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).client(client_scope_optional.getClientId())
|
||||||
|
.detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "FirstCC")
|
||||||
|
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "LastCC")
|
||||||
|
.assertEvent();
|
||||||
|
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
|
|
|
@ -9,24 +9,30 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import org.hamcrest.MatcherAssert;
|
import org.hamcrest.MatcherAssert;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper;
|
import org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderSyncMode;
|
import org.keycloak.models.IdentityProviderSyncMode;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.forms.VerifyProfileTest;
|
import org.keycloak.testsuite.forms.VerifyProfileTest;
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
import org.keycloak.testsuite.util.MailServer;
|
import org.keycloak.testsuite.util.MailServer;
|
||||||
import org.keycloak.testsuite.util.MailServerConfiguration;
|
import org.keycloak.testsuite.util.MailServerConfiguration;
|
||||||
import org.keycloak.testsuite.util.SecondBrowser;
|
import org.keycloak.testsuite.util.SecondBrowser;
|
||||||
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.NoSuchElementException;
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
@ -56,6 +62,9 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
||||||
@SecondBrowser
|
@SecondBrowser
|
||||||
protected WebDriver driver2;
|
protected WebDriver driver2;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
protected void enableDynamicUserProfile() {
|
protected void enableDynamicUserProfile() {
|
||||||
|
|
||||||
RealmResource rr = adminClient.realm(bc.consumerRealmName());
|
RealmResource rr = adminClient.realm(bc.consumerRealmName());
|
||||||
|
@ -971,7 +980,85 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
||||||
//test if the user has verified email
|
//test if the user has verified email
|
||||||
assertTrue(consumerRealm.users().get(linkedUserId).toRepresentation().isEmailVerified());
|
assertTrue(consumerRealm.users().get(linkedUserId).toRepresentation().isEmailVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEventsOnUpdateProfileNoEmailChange() {
|
||||||
|
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||||
|
|
||||||
|
createUser(bc.providerRealmName(), "no-first-name", "password", null, "LastName", "no-first-name@localhost.com");
|
||||||
|
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||||
|
log.debug("Clicking social " + bc.getIDPAlias());
|
||||||
|
loginPage.clickSocial(bc.getIDPAlias());
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||||
|
log.debug("Logging in");
|
||||||
|
loginPage.login("no-first-name", "password");
|
||||||
|
|
||||||
|
waitForPage(driver, "update account information", false);
|
||||||
|
|
||||||
|
updateAccountInformationPage.assertCurrent();
|
||||||
|
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
|
||||||
|
waitForAccountManagementTitle();
|
||||||
|
accountUpdateProfilePage.assertCurrent();
|
||||||
|
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||||
|
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||||
|
Assert.assertEquals("no-first-name@localhost.com", accountUpdateProfilePage.getEmail());
|
||||||
|
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
|
||||||
|
|
||||||
|
RealmRepresentation consumerRealmRep = adminClient.realm(bc.consumerRealmName()).toRepresentation();
|
||||||
|
events.expectAccount(EventType.LOGIN).realm(consumerRealmRep).user(Matchers.any(String.class)).session(Matchers.any(String.class))
|
||||||
|
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")
|
||||||
|
.detail(Details.REGISTER_METHOD, "broker")
|
||||||
|
.assertEvent(getFirstConsumerEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEventsOnUpdateProfileWithEmailChange() {
|
||||||
|
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||||
|
|
||||||
|
createUser(bc.providerRealmName(), "no-first-name", "password", null, "LastName", "no-first-name@localhost.com");
|
||||||
|
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||||
|
log.debug("Clicking social " + bc.getIDPAlias());
|
||||||
|
loginPage.clickSocial(bc.getIDPAlias());
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||||
|
log.debug("Logging in");
|
||||||
|
loginPage.login("no-first-name", "password");
|
||||||
|
|
||||||
|
waitForPage(driver, "update account information", false);
|
||||||
|
|
||||||
|
updateAccountInformationPage.assertCurrent();
|
||||||
|
updateAccountInformationPage.updateAccountInformation("new-email@localhost.com","FirstName", "LastName");
|
||||||
|
waitForAccountManagementTitle();
|
||||||
|
accountUpdateProfilePage.assertCurrent();
|
||||||
|
Assert.assertEquals("FirstName", accountUpdateProfilePage.getFirstName());
|
||||||
|
Assert.assertEquals("LastName", accountUpdateProfilePage.getLastName());
|
||||||
|
Assert.assertEquals("new-email@localhost.com", accountUpdateProfilePage.getEmail());
|
||||||
|
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
|
||||||
|
|
||||||
|
RealmRepresentation consumerRealmRep = adminClient.realm(bc.consumerRealmName()).toRepresentation();
|
||||||
|
events.expectAccount(EventType.UPDATE_EMAIL).realm(consumerRealmRep).user((String)null).session((String) null)
|
||||||
|
.detail(Details.CONTEXT, UserProfileContext.IDP_REVIEW.name())
|
||||||
|
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")
|
||||||
|
.detail(Details.PREVIOUS_EMAIL, "no-first-name@localhost.com")
|
||||||
|
.detail(Details.UPDATED_EMAIL, "new-email@localhost.com")
|
||||||
|
.assertEvent(getFirstConsumerEvent());
|
||||||
|
events.expectAccount(EventType.LOGIN).realm(consumerRealmRep).user(Matchers.any(String.class)).session(Matchers.any(String.class))
|
||||||
|
.detail(Details.IDENTITY_PROVIDER_USERNAME, "no-first-name")
|
||||||
|
.detail(Details.REGISTER_METHOD, "broker")
|
||||||
|
.assertEvent(events.poll());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EventRepresentation getFirstConsumerEvent() {
|
||||||
|
String providerRealmId = adminClient.realm(bc.providerRealmName()).toRepresentation().getId();
|
||||||
|
EventRepresentation er = events.poll();
|
||||||
|
while(er != null && providerRealmId.equals(er.getRealmId())) {
|
||||||
|
er = events.poll();
|
||||||
|
}
|
||||||
|
return er;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refers to in old test suite: org.keycloak.testsuite.broker.AbstractKeycloakIdentityProviderTest.testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail
|
* Refers to in old test suite: org.keycloak.testsuite.broker.AbstractKeycloakIdentityProviderTest.testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail
|
||||||
|
@ -991,6 +1078,7 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
||||||
loginPage.login("no-first-name", "password");
|
loginPage.login("no-first-name", "password");
|
||||||
|
|
||||||
waitForPage(driver, "update account information", false);
|
waitForPage(driver, "update account information", false);
|
||||||
|
|
||||||
updateAccountInformationPage.assertCurrent();
|
updateAccountInformationPage.assertCurrent();
|
||||||
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
|
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
|
||||||
waitForAccountManagementTitle();
|
waitForAccountManagementTitle();
|
||||||
|
@ -1000,7 +1088,6 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
|
||||||
Assert.assertEquals("no-first-name@localhost.com", accountUpdateProfilePage.getEmail());
|
Assert.assertEquals("no-first-name@localhost.com", accountUpdateProfilePage.getEmail());
|
||||||
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
|
Assert.assertEquals("no-first-name", accountUpdateProfilePage.getUsername());
|
||||||
|
|
||||||
|
|
||||||
logoutFromRealm(getProviderRoot(), bc.providerRealmName());
|
logoutFromRealm(getProviderRoot(), bc.providerRealmName());
|
||||||
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
|
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
|
||||||
createUser(bc.providerRealmName(), "no-last-name", "password", "FirstName", null, "no-last-name@localhost.com");
|
createUser(bc.providerRealmName(), "no-last-name", "password", "FirstName", null, "no-last-name@localhost.com");
|
||||||
|
|
|
@ -48,7 +48,6 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
@ -58,7 +57,8 @@ import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
import org.keycloak.userprofile.UserProfileSpi;
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
|
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -321,9 +321,12 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
verifyProfilePage.update("First", "Last", "Department");
|
verifyProfilePage.update("First", "Last", "Department");
|
||||||
//event after profile is updated
|
//event after profile is updated
|
||||||
|
// we also test additional attribute configured to be audited in the event
|
||||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).user(user5Id)
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).user(user5Id)
|
||||||
|
.detail(Details.CONTEXT, UserProfileContext.UPDATE_PROFILE.name())
|
||||||
.detail(Details.PREVIOUS_FIRST_NAME, "ExistingFirst").detail(Details.UPDATED_FIRST_NAME, "First")
|
.detail(Details.PREVIOUS_FIRST_NAME, "ExistingFirst").detail(Details.UPDATED_FIRST_NAME, "First")
|
||||||
.detail(Details.PREVIOUS_LAST_NAME, "ExistingLast").detail(Details.UPDATED_LAST_NAME, "Last")
|
.detail(Details.PREVIOUS_LAST_NAME, "ExistingLast").detail(Details.UPDATED_LAST_NAME, "Last")
|
||||||
|
.detail(Details.PREF_UPDATED+"department", "Department")
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,6 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.Profile;
|
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.component.ComponentValidationException;
|
import org.keycloak.component.ComponentValidationException;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -53,13 +52,9 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
|
||||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||||
import org.keycloak.userprofile.AttributeGroupMetadata;
|
import org.keycloak.userprofile.AttributeGroupMetadata;
|
||||||
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
|
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
|
||||||
import org.keycloak.userprofile.UserProfileSpi;
|
|
||||||
import org.keycloak.userprofile.config.UPAttribute;
|
import org.keycloak.userprofile.config.UPAttribute;
|
||||||
import org.keycloak.userprofile.config.UPAttributePermissions;
|
import org.keycloak.userprofile.config.UPAttributePermissions;
|
||||||
import org.keycloak.userprofile.config.UPAttributeRequired;
|
import org.keycloak.userprofile.config.UPAttributeRequired;
|
||||||
|
@ -459,8 +454,15 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
|
|
||||||
profile = provider.create(UserProfileContext.ACCOUNT, attributes, user);
|
profile = provider.create(UserProfileContext.ACCOUNT, attributes, user);
|
||||||
Set<String> attributesUpdated = new HashSet<>();
|
Set<String> attributesUpdated = new HashSet<>();
|
||||||
|
Map<String, String> attributesUpdatedOldValues = new HashMap<>();
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
attributesUpdatedOldValues.put(UserModel.FIRST_NAME, "Joe");
|
||||||
|
attributesUpdatedOldValues.put(UserModel.LAST_NAME, "Doe");
|
||||||
|
|
||||||
|
profile.update((attributeName, userModel, oldValue) -> {
|
||||||
|
assertTrue(attributesUpdated.add(attributeName));
|
||||||
|
assertEquals(attributesUpdatedOldValues.get(attributeName), getSingleValue(oldValue));
|
||||||
|
assertEquals(attributes.get(attributeName), userModel.getFirstAttribute(attributeName));
|
||||||
|
});
|
||||||
|
|
||||||
assertThat(attributesUpdated, containsInAnyOrder(UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.EMAIL));
|
assertThat(attributesUpdated, containsInAnyOrder(UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.EMAIL));
|
||||||
|
|
||||||
|
@ -470,13 +472,19 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
profile = provider.create(UserProfileContext.ACCOUNT, attributes, user);
|
profile = provider.create(UserProfileContext.ACCOUNT, attributes, user);
|
||||||
|
|
||||||
attributesUpdated.clear();
|
attributesUpdated.clear();
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
|
|
||||||
assertThat(attributesUpdated, containsInAnyOrder("business.address"));
|
assertThat(attributesUpdated, containsInAnyOrder("business.address"));
|
||||||
|
|
||||||
assertEquals("fixed-business-address", user.getFirstAttribute("business.address"));
|
assertEquals("fixed-business-address", user.getFirstAttribute("business.address"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getSingleValue(List<String> vals) {
|
||||||
|
if(vals==null || vals.isEmpty())
|
||||||
|
return null;
|
||||||
|
return vals.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadonlyUpdates() {
|
public void testReadonlyUpdates() {
|
||||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyUpdates);
|
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyUpdates);
|
||||||
|
@ -505,7 +513,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
|
|
||||||
Set<String> attributesUpdated = new HashSet<>();
|
Set<String> attributesUpdated = new HashSet<>();
|
||||||
|
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
|
|
||||||
assertThat(attributesUpdated, containsInAnyOrder("department"));
|
assertThat(attributesUpdated, containsInAnyOrder("department"));
|
||||||
|
|
||||||
|
@ -556,7 +564,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
|
|
||||||
Set<String> attributesUpdated = new HashSet<>();
|
Set<String> attributesUpdated = new HashSet<>();
|
||||||
|
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
assertThat(attributesUpdated, containsInAnyOrder("department", "address", "phone"));
|
assertThat(attributesUpdated, containsInAnyOrder("department", "address", "phone"));
|
||||||
|
|
||||||
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}},"
|
provider.setConfiguration("{\"attributes\": [{\"name\": \"department\", \"permissions\": {\"edit\": [\"admin\"]}},"
|
||||||
|
@ -566,7 +574,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
attributes.put("department", "foo");
|
attributes.put("department", "foo");
|
||||||
attributes.put("phone", "foo");
|
attributes.put("phone", "foo");
|
||||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
assertThat(attributesUpdated, containsInAnyOrder("department", "phone"));
|
assertThat(attributesUpdated, containsInAnyOrder("department", "phone"));
|
||||||
assertTrue(user.getAttributes().containsKey("address"));
|
assertTrue(user.getAttributes().containsKey("address"));
|
||||||
|
|
||||||
|
@ -578,7 +586,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
attributes.put("address", "bar");
|
attributes.put("address", "bar");
|
||||||
attributesUpdated.clear();
|
attributesUpdated.clear();
|
||||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
assertThat(attributesUpdated, containsInAnyOrder("address"));
|
assertThat(attributesUpdated, containsInAnyOrder("address"));
|
||||||
assertEquals("bar", user.getFirstAttribute("address"));
|
assertEquals("bar", user.getFirstAttribute("address"));
|
||||||
assertEquals("foo", user.getFirstAttribute("phone"));
|
assertEquals("foo", user.getFirstAttribute("phone"));
|
||||||
|
@ -587,7 +595,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
attributes.remove("address");
|
attributes.remove("address");
|
||||||
attributesUpdated.clear();
|
attributesUpdated.clear();
|
||||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
assertThat(attributesUpdated, containsInAnyOrder("address"));
|
assertThat(attributesUpdated, containsInAnyOrder("address"));
|
||||||
assertFalse(user.getAttributes().containsKey("address"));
|
assertFalse(user.getAttributes().containsKey("address"));
|
||||||
assertTrue(user.getAttributes().containsKey("phone"));
|
assertTrue(user.getAttributes().containsKey("phone"));
|
||||||
|
@ -597,7 +605,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
attributes.put(prefixedAttributeName, "foo");
|
attributes.put(prefixedAttributeName, "foo");
|
||||||
attributesUpdated.clear();
|
attributesUpdated.clear();
|
||||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||||
profile.update((attributeName, userModel) -> assertTrue(attributesUpdated.add(attributeName)));
|
profile.update((attributeName, userModel, oldValue) -> assertTrue(attributesUpdated.add(attributeName)));
|
||||||
assertTrue(attributesUpdated.isEmpty());
|
assertTrue(attributesUpdated.isEmpty());
|
||||||
assertFalse(user.getAttributes().containsKey("prefixedAttributeName"));
|
assertFalse(user.getAttributes().containsKey("prefixedAttributeName"));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue