diff --git a/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java b/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java index 7cd2e1739c..c1207be3e6 100755 --- a/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java +++ b/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java @@ -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 includedEvents; private EventListenerTransaction tx = new EventListenerTransaction(null, this::sendEmail); + private final KeycloakSessionFactory sessionFactory; - public EmailEventListenerProvider(KeycloakSession session, EmailTemplateProvider emailTemplateProvider, Set includedEvents) { + public EmailEventListenerProvider(KeycloakSession session, Set 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,15 +71,37 @@ public class EmailEventListenerProvider implements EventListenerProvider { } private void sendEmail(Event event) { - RealmModel realm = model.getRealm(event.getRealmId()); - UserModel user = session.users().getUserById(realm, event.getUserId()); - if (user != null && user.getEmail() != null && user.isEmailVerified()) { - try { - emailTemplateProvider.setRealm(realm).setUser(user).sendEvent(event); - } catch (EmailException e) { - log.error("Failed to send type mail", e); + 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 diff --git a/services/src/main/java/org/keycloak/events/email/EmailEventListenerProviderFactory.java b/services/src/main/java/org/keycloak/events/email/EmailEventListenerProviderFactory.java index e99f951eb1..c0b4d00cc1 100755 --- a/services/src/main/java/org/keycloak/events/email/EmailEventListenerProviderFactory.java +++ b/services/src/main/java/org/keycloak/events/email/EmailEventListenerProviderFactory.java @@ -40,6 +40,9 @@ import java.util.Set; public class EmailEventListenerProviderFactory implements EventListenerProviderFactory { private static final Set 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 diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EmailEventListenerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EmailEventListenerTest.java new file mode 100644 index 0000000000..5959ddac47 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EmailEventListenerTest.java @@ -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()); + } +}