Handling events after transaction completion using a separate session
Closes #15656
This commit is contained in:
parent
d4604984d0
commit
9e46b9e43f
3 changed files with 127 additions and 15 deletions
|
@ -17,7 +17,10 @@
|
|||
|
||||
package org.keycloak.events.email;
|
||||
|
||||
import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Resteasy;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailTemplateProvider;
|
||||
import org.keycloak.events.Event;
|
||||
|
@ -25,7 +28,12 @@ import org.keycloak.events.EventListenerProvider;
|
|||
import org.keycloak.events.EventListenerTransaction;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -41,17 +49,16 @@ public class EmailEventListenerProvider implements EventListenerProvider {
|
|||
|
||||
private KeycloakSession session;
|
||||
private RealmProvider model;
|
||||
private EmailTemplateProvider emailTemplateProvider;
|
||||
private Set<EventType> includedEvents;
|
||||
private EventListenerTransaction tx = new EventListenerTransaction(null, this::sendEmail);
|
||||
private final KeycloakSessionFactory sessionFactory;
|
||||
|
||||
public EmailEventListenerProvider(KeycloakSession session, EmailTemplateProvider emailTemplateProvider, Set<EventType> includedEvents) {
|
||||
public EmailEventListenerProvider(KeycloakSession session, Set<EventType> includedEvents) {
|
||||
this.session = session;
|
||||
this.model = session.realms();
|
||||
this.emailTemplateProvider = emailTemplateProvider;
|
||||
this.includedEvents = includedEvents;
|
||||
|
||||
this.session.getTransactionManager().enlistAfterCompletion(tx);
|
||||
this.sessionFactory = session.getKeycloakSessionFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,16 +71,38 @@ public class EmailEventListenerProvider implements EventListenerProvider {
|
|||
}
|
||||
|
||||
private void sendEmail(Event event) {
|
||||
RealmModel realm = model.getRealm(event.getRealmId());
|
||||
HttpRequest request = session.getContext().getHttpRequest();
|
||||
|
||||
runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
KeycloakContext context = session.getContext();
|
||||
RealmModel realm = session.realms().getRealm(event.getRealmId());
|
||||
|
||||
context.setRealm(realm);
|
||||
|
||||
String clientId = event.getClientId();
|
||||
|
||||
if (clientId != null) {
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
context.setClient(client);
|
||||
}
|
||||
|
||||
Resteasy.pushContext(HttpRequest.class, request);
|
||||
|
||||
UserModel user = session.users().getUserById(realm, event.getUserId());
|
||||
|
||||
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
|
||||
try {
|
||||
EmailTemplateProvider emailTemplateProvider = session.getProvider(EmailTemplateProvider.class);
|
||||
emailTemplateProvider.setRealm(realm).setUser(user).sendEvent(event);
|
||||
} catch (EmailException e) {
|
||||
log.error("Failed to send type mail", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||
|
|
|
@ -40,6 +40,9 @@ import java.util.Set;
|
|||
public class EmailEventListenerProviderFactory implements EventListenerProviderFactory {
|
||||
|
||||
private static final Set<EventType> SUPPORTED_EVENTS = new HashSet<>();
|
||||
|
||||
public static final String ID = "email";
|
||||
|
||||
static {
|
||||
Collections.addAll(SUPPORTED_EVENTS, EventType.LOGIN_ERROR, EventType.UPDATE_PASSWORD, EventType.REMOVE_TOTP, EventType.UPDATE_TOTP);
|
||||
}
|
||||
|
@ -48,8 +51,7 @@ public class EmailEventListenerProviderFactory implements EventListenerProviderF
|
|||
|
||||
@Override
|
||||
public EventListenerProvider create(KeycloakSession session) {
|
||||
EmailTemplateProvider emailTemplateProvider = session.getProvider(EmailTemplateProvider.class);
|
||||
return new EmailEventListenerProvider(session, emailTemplateProvider, includedEvents);
|
||||
return new EmailEventListenerProvider(session, includedEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,7 +84,7 @@ public class EmailEventListenerProviderFactory implements EventListenerProviderF
|
|||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "email";
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* 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.testsuite.admin.event;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.email.EmailEventListenerProviderFactory;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
||||
import org.keycloak.testsuite.util.GreenMailRule;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public class EmailEventListenerTest extends AbstractEventTest {
|
||||
|
||||
@Rule
|
||||
public GreenMailRule greenMail = new GreenMailRule();
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
RealmRepresentation realm = testRealmResource().toRepresentation();
|
||||
|
||||
realm.setSmtpServer(suiteContext.getSmtpServer());
|
||||
|
||||
testRealmResource().update(realm);
|
||||
|
||||
configRep.setEventsEnabled(true);
|
||||
configRep.setEventsListeners(List.of(EmailEventListenerProviderFactory.ID));
|
||||
saveConfig();
|
||||
RealmResource realmResource = testRealmResource();
|
||||
|
||||
realmResource.users().create(UserBuilder.create()
|
||||
.username("alice")
|
||||
.email("alice@keycloak.org")
|
||||
.emailVerified(true)
|
||||
.password("alice").build());
|
||||
|
||||
realmResource.clearEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventAttributesTest() {
|
||||
accountPage.navigateTo();
|
||||
loginPage.form().login("alice", "invalid");
|
||||
loginPage.assertCurrent();
|
||||
assertNotNull(greenMail.getLastReceivedMessage());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue