HI,
I would like to add some attributes to the UserModel in the my custom EventListenerProvider, but they are not saved.
Example code:
public class MyEventListenerProvider implements EventListenerProvider {
@Override
public void onEvent(Event event) {
UserModel user = determineUserFromEvent(Event event); // --> for example session.userLocalStorage().getUserByUsername(username, getRealm());
//add some Attributes
user.setSingleAttribute("attribute1" , "value1");
user.setSingleAttribute("attribute2" , "value2");
//How to persist/save these changes to the Database?
//user.persist/flush/save();
}
}
How can I save the attributes to the database?
regards
Daniel
Have a look at this:
package ch.hcuge.listener;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.hcuge.common.UserUtil;
/**
* Listener KeyCloak for the HUG account.
* <ul>
* <li>Keep a 'small' connections history</li>
* <li>Set phone number masked on some events</li>
* </ul>
*/
public class HUGListener implements EventListenerProvider {
public static final String ID = "hug-listener";
private static final Logger logger = Logger.getLogger(HUGListener.class);
public static final Set<EventType> USER_EVENT_TYPES_4_LOGIN_HISTORY = Set.of(EventType.REGISTER, EventType.IDENTITY_PROVIDER_FIRST_LOGIN, EventType.LOGIN, EventType.IDENTITY_PROVIDER_LOGIN, EventType.IDENTITY_PROVIDER_POST_LOGIN, EventType.CLIENT_LOGIN);
public static final Set<EventType> USER_EVENT_TYPES_4_UPDATE_PROFILE = Set.of(EventType.REGISTER, EventType.UPDATE_PROFILE, EventType.IDENTITY_PROVIDER_FIRST_LOGIN, EventType.IDENTITY_PROVIDER_LOGIN, EventType.IDENTITY_PROVIDER_POST_LOGIN);
protected static Set<EventType> SUPPORTED_USER_EVENT_TYPES = Set.of();
static {
SUPPORTED_USER_EVENT_TYPES = Stream.concat(SUPPORTED_USER_EVENT_TYPES.stream(), USER_EVENT_TYPES_4_LOGIN_HISTORY.stream()).collect(Collectors.toSet());
SUPPORTED_USER_EVENT_TYPES = Stream.concat(SUPPORTED_USER_EVENT_TYPES.stream(), USER_EVENT_TYPES_4_UPDATE_PROFILE.stream()).collect(Collectors.toSet());
}
private KeycloakSession session; // not final for mockito
public HUGListener(KeycloakSession session) {
this.session = session;
}
@Override
@SuppressWarnings("deprecation")
public void onEvent(Event event) {
if (!SUPPORTED_USER_EVENT_TYPES.contains(event.getType())) {
logger.debugf("Unsupported user event : %s", event.getType());
onNotSupportedEvent(event); // just to easy tests with mockito
return;
}
if (session.realms() != null && session.users() != null && StringUtils.isNotBlank(event.getRealmId()) && StringUtils.isNotBlank(event.getUserId())) {
RealmModel realm = session.realms().getRealm(event.getRealmId());
if (realm != null) {
UserModel user = session.users().getUserById(event.getUserId(), realm);
if (user != null) {
onAnySupportedEvent(event, user);
if (USER_EVENT_TYPES_4_LOGIN_HISTORY.contains(event.getType())) {
onLoginEvent(event, user);
}
if (USER_EVENT_TYPES_4_UPDATE_PROFILE.contains(event.getType())) {
onUpdateProfileEvent(event, user);
}
try {
logger.debugf("%s : %s", event.getType(), UserUtil.toJson(user));
} catch (JsonProcessingException e) {
// NOOP
}
}
}
}
}
protected void onAnySupportedEvent(Event event, UserModel user) {
logger.debugf("Supported user event : %s", event.getType());
// To be implemented if an action has to be always executed for the supported events
}
protected void onLoginEvent(Event event, UserModel user) {
// Set last login and update login history
UserUtil.setLastLogin(user, DateTimeFormatter.ISO_DATE_TIME.format(OffsetDateTime.now(ZoneOffset.UTC)));
}
protected void onUpdateProfileEvent(Event event, UserModel user) {
UserUtil.setPhoneNumberMasked(user);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
ResourceType resourceType = event.getResourceType();
Boolean supportedAdminEvent = Boolean.FALSE;
if (ResourceType.USER == resourceType) {
supportedAdminEvent = Boolean.TRUE;
onAminEvent4UserResource(event);
}
if (Boolean.TRUE.equals(supportedAdminEvent)) {
logger.debugf("Supported admin event : ressource type : [%s], ressource path : [%s], operation type : [%s]", event.getResourceTypeAsString(), event.getResourcePath(), event.getOperationType());
} else {
logger.debugf("Unsupported admin event : ressource type : [%s], ressource path : [%s], operation type : [%s]", event.getResourceTypeAsString(), event.getResourcePath(), event.getOperationType());
}
}
@SuppressWarnings("deprecation")
protected void onAminEvent4UserResource(AdminEvent event) {
RealmModel realm = session.realms().getRealm(event.getRealmId());
String userId = event.getResourcePath().substring("users/".length());
UserModel user = session.users().getUserById(userId, realm);
UserUtil.setPhoneNumberMasked(user);
}
@Override
public void close() {
// NOOP
}
public void onNotSupportedEvent(Event event) {
// just to easy tests with mockito
}
}
Especially:
Where called methods of UserUtil is:
Thank you for the answer. But in my case Keycloak does not commit the new attributes to the database. And when I reload the user, the attributes are missing.
That’s very odd, my event listener also just uses the setSingleAttribute ( keycloak-last-login-event-listener/LastLoginEventListenerProvider.java at main · ThoreKr/keycloak-last-login-event-listener · GitHub ).
Are you sure the event listener is registered correctly?
Hi @dschneider i have your same issue on a keycloak 16.1.1
I’m trying to use this spi.
The code runs without errors but Keycloak doesn’t save the user attribute on the user profile.
Have you resolved your issue?
@dvlpphb @dschneider I am in the same situation - trying to set a user attribute from an SPI in KC 16.1, an EventListener in my case. Extension is registered, doesn’t throw any errors but attributes are not set. If you managed to figure this one out I’d appreciate to know how.
Thanks.
Seems like setSingleAttribute works fine and persists the attribute with LOGIN event, but not with REGISTER